emdash 0.29.0 → 0.30.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 (839) 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-b8WIiGU4.mjs} +253 -153
  11. package/dist/api-b8WIiGU4.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-CmIJK9j8.mjs} +34 -37
  16. package/dist/apply-CmIJK9j8.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 +326 -110
  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 +15 -14
  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 +16 -21
  54. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/bylines/index.mjs +15 -14
  56. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  57. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -12
  58. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  60. package/dist/astro/routes/api/admin/comments/bulk.mjs +11 -10
  61. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  63. package/dist/astro/routes/api/admin/comments/index.mjs +11 -10
  64. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  65. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  66. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  67. package/dist/astro/routes/api/admin/media-usage/repair.mjs +12 -10
  68. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  70. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  71. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +40 -39
  72. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +40 -39
  74. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  75. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +45 -39
  76. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts +8 -0
  78. package/dist/astro/routes/api/admin/plugins/_id_/mcp.d.mts.map +1 -0
  79. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +39 -0
  80. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs.map +1 -0
  81. package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts +9 -0
  82. package/dist/astro/routes/api/admin/plugins/_id_/settings.d.mts.map +1 -0
  83. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +78 -0
  84. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -0
  85. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +39 -38
  86. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +42 -39
  89. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/plugins/index.mjs +45 -39
  91. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  93. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +39 -38
  94. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +45 -40
  97. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +39 -38
  99. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +39 -38
  101. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  102. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +43 -40
  104. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  105. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +39 -38
  106. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  107. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  108. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +44 -41
  109. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  110. package/dist/astro/routes/api/admin/plugins/updates.mjs +40 -39
  111. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  112. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +39 -38
  113. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  114. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  115. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +39 -38
  116. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  117. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  118. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +4 -4
  119. package/dist/astro/routes/api/admin/users/_id_/index.mjs +9 -8
  120. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  121. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
  122. package/dist/astro/routes/api/admin/users/index.mjs +9 -8
  123. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  124. package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
  125. package/dist/astro/routes/api/auth/invite/accept.mjs +4 -4
  126. package/dist/astro/routes/api/auth/invite/complete.mjs +13 -12
  127. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  128. package/dist/astro/routes/api/auth/invite/index.mjs +10 -9
  129. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  130. package/dist/astro/routes/api/auth/invite/register-options.mjs +12 -11
  131. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  132. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  133. package/dist/astro/routes/api/auth/magic-link/send.mjs +12 -11
  134. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  135. package/dist/astro/routes/api/auth/magic-link/verify.mjs +5 -5
  136. package/dist/astro/routes/api/auth/me.mjs +9 -8
  137. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  138. package/dist/astro/routes/api/auth/mode.d.mts.map +1 -1
  139. package/dist/astro/routes/api/auth/mode.mjs +6 -3
  140. package/dist/astro/routes/api/auth/mode.mjs.map +1 -1
  141. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +15 -5
  142. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
  143. package/dist/astro/routes/api/auth/oauth/_provider_.d.mts.map +1 -1
  144. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +11 -5
  145. package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
  146. package/dist/astro/routes/api/auth/passkey/_id_.mjs +8 -7
  147. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  148. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  149. package/dist/astro/routes/api/auth/passkey/options.mjs +13 -12
  150. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  151. package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -10
  152. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  153. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -11
  154. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  155. package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -11
  156. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  157. package/dist/astro/routes/api/auth/signup/complete.mjs +13 -12
  158. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  159. package/dist/astro/routes/api/auth/signup/request.mjs +12 -11
  160. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  161. package/dist/astro/routes/api/auth/signup/verify.mjs +4 -4
  162. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -13
  163. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  164. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -12
  165. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
  166. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +5 -5
  167. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs.map +1 -1
  168. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +5 -4
  169. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  170. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -5
  171. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs.map +1 -1
  172. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +5 -5
  173. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs.map +1 -1
  174. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +13 -12
  175. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  176. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +10 -8
  177. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  178. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts.map +1 -1
  179. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +20 -19
  180. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  181. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +18 -17
  182. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  183. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -4
  184. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs.map +1 -1
  185. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +5 -5
  186. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs.map +1 -1
  187. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +10 -8
  188. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  189. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  190. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +18 -17
  191. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  192. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +5 -5
  193. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs.map +1 -1
  194. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +5 -4
  195. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  196. package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
  197. package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -9
  198. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  199. package/dist/astro/routes/api/content/_collection_/authors.mjs +5 -5
  200. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -1
  201. package/dist/astro/routes/api/content/_collection_/index.d.mts.map +1 -1
  202. package/dist/astro/routes/api/content/_collection_/index.mjs +12 -10
  203. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  204. package/dist/astro/routes/api/content/_collection_/trash.mjs +10 -9
  205. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  206. package/dist/astro/routes/api/dashboard.mjs +8 -8
  207. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  208. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  209. package/dist/astro/routes/api/import/probe.mjs +17 -16
  210. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  211. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  212. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -12
  213. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  214. package/dist/astro/routes/api/import/wordpress/execute.mjs +12 -12
  215. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -11
  216. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  217. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -10
  218. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  219. package/dist/astro/routes/api/import/wordpress/prepare.mjs +11 -10
  220. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  221. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -12
  222. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  223. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +11 -10
  224. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  225. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  226. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +17 -16
  227. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  228. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  229. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -12
  230. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  231. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +23 -22
  232. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  233. package/dist/astro/routes/api/manifest.mjs +7 -8
  234. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  235. package/dist/astro/routes/api/mcp.d.mts.map +1 -1
  236. package/dist/astro/routes/api/mcp.mjs +149 -45
  237. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  238. package/dist/astro/routes/api/media/_id_/confirm.mjs +9 -8
  239. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  240. package/dist/astro/routes/api/media/_id_/usage.d.mts +8 -0
  241. package/dist/astro/routes/api/media/_id_/usage.d.mts.map +1 -0
  242. package/dist/astro/routes/api/media/_id_/usage.mjs +37 -0
  243. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -0
  244. package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
  245. package/dist/astro/routes/api/media/_id_.mjs +26 -10
  246. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  247. package/dist/astro/routes/api/media/file/_...key_.mjs +4 -3
  248. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  249. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  250. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  251. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  252. package/dist/astro/routes/api/media/upload-url.mjs +10 -9
  253. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  254. package/dist/astro/routes/api/media.d.mts.map +1 -1
  255. package/dist/astro/routes/api/media.mjs +31 -11
  256. package/dist/astro/routes/api/media.mjs.map +1 -1
  257. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +10 -9
  258. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  259. package/dist/astro/routes/api/menus/_name_/items.mjs +10 -9
  260. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  261. package/dist/astro/routes/api/menus/_name_/reorder.mjs +10 -9
  262. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  263. package/dist/astro/routes/api/menus/_name_/translations.mjs +10 -9
  264. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  265. package/dist/astro/routes/api/menus/_name_.mjs +10 -9
  266. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  267. package/dist/astro/routes/api/menus/index.mjs +10 -9
  268. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  269. package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
  270. package/dist/astro/routes/api/oauth/authorize.mjs +7 -8
  271. package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
  272. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  273. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  274. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  275. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  276. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  277. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  278. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  279. package/dist/astro/routes/api/openapi.json.mjs +52 -24
  280. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  281. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
  282. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +20 -32
  283. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  284. package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
  285. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  286. package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
  287. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  288. package/dist/astro/routes/api/redirects/_id_.mjs +13 -12
  289. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  290. package/dist/astro/routes/api/redirects/index.mjs +13 -12
  291. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  292. package/dist/astro/routes/api/relations/_id_/index.mjs +16 -15
  293. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  294. package/dist/astro/routes/api/relations/_id_/translations.mjs +11 -11
  295. package/dist/astro/routes/api/relations/index.mjs +16 -15
  296. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  297. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  298. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -4
  299. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs.map +1 -1
  300. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +39 -38
  301. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  302. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +39 -38
  303. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  304. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +39 -38
  305. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  306. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +39 -38
  307. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  308. package/dist/astro/routes/api/schema/collections/index.mjs +39 -38
  309. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  310. package/dist/astro/routes/api/schema/index.mjs +9 -9
  311. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +39 -38
  312. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  313. package/dist/astro/routes/api/schema/orphans/index.mjs +39 -38
  314. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  315. package/dist/astro/routes/api/search/enable.mjs +13 -12
  316. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  317. package/dist/astro/routes/api/search/index.mjs +12 -11
  318. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  319. package/dist/astro/routes/api/search/rebuild.mjs +13 -12
  320. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  321. package/dist/astro/routes/api/search/stats.mjs +8 -8
  322. package/dist/astro/routes/api/search/suggest.mjs +12 -11
  323. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  324. package/dist/astro/routes/api/sections/_slug_.mjs +12 -11
  325. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  326. package/dist/astro/routes/api/sections/index.mjs +12 -11
  327. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  328. package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts +9 -0
  329. package/dist/astro/routes/api/settings/backups/archives/_name_.d.mts.map +1 -0
  330. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +48 -0
  331. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -0
  332. package/dist/astro/routes/api/settings/backups/archives/index.d.mts +8 -0
  333. package/dist/astro/routes/api/settings/backups/archives/index.d.mts.map +1 -0
  334. package/dist/astro/routes/api/settings/backups/archives/index.mjs +23 -0
  335. package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -0
  336. package/dist/astro/routes/api/settings/backups/export.d.mts +8 -0
  337. package/dist/astro/routes/api/settings/backups/export.d.mts.map +1 -0
  338. package/dist/astro/routes/api/settings/backups/export.mjs +35 -0
  339. package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -0
  340. package/dist/astro/routes/api/settings/backups/index.d.mts +9 -0
  341. package/dist/astro/routes/api/settings/backups/index.d.mts.map +1 -0
  342. package/dist/astro/routes/api/settings/backups/index.mjs +52 -0
  343. package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -0
  344. package/dist/astro/routes/api/settings/email.mjs +5 -5
  345. package/dist/astro/routes/api/settings.mjs +14 -13
  346. package/dist/astro/routes/api/settings.mjs.map +1 -1
  347. package/dist/astro/routes/api/setup/admin-verify.mjs +19 -15
  348. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  349. package/dist/astro/routes/api/setup/admin.mjs +18 -14
  350. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  351. package/dist/astro/routes/api/setup/dev-bypass.mjs +26 -26
  352. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  353. package/dist/astro/routes/api/setup/index.mjs +28 -27
  354. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  355. package/dist/astro/routes/api/setup/status.mjs +5 -5
  356. package/dist/astro/routes/api/snapshot.mjs +8 -215
  357. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  358. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +14 -13
  359. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  360. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +14 -13
  361. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  362. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +14 -13
  363. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  364. package/dist/astro/routes/api/taxonomies/index.mjs +14 -13
  365. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  366. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  367. package/dist/astro/routes/api/typegen.mjs +6 -6
  368. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  369. package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts.map +1 -1
  370. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +4 -3
  371. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs.map +1 -1
  372. package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts.map +1 -1
  373. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +4 -3
  374. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs.map +1 -1
  375. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +9 -8
  376. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  377. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -11
  378. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  379. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -11
  380. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  381. package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -7
  382. package/dist/astro/routes/api/widget-areas/index.mjs +12 -11
  383. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  384. package/dist/astro/routes/api/widget-components.mjs +4 -4
  385. package/dist/astro/routes/robots.txt.mjs +7 -7
  386. package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
  387. package/dist/astro/routes/sitemap.xml.mjs +8 -8
  388. package/dist/astro/types.d.mts +38 -17
  389. package/dist/astro/types.d.mts.map +1 -1
  390. package/dist/auth/providers/github.d.mts +1 -1
  391. package/dist/auth/providers/google.d.mts +1 -1
  392. package/dist/{authorize-BmCwQCib.mjs → authorize-WxsiePzy.mjs} +7 -4
  393. package/dist/authorize-WxsiePzy.mjs.map +1 -0
  394. package/dist/backup-S8kFWtwD.mjs +241 -0
  395. package/dist/backup-S8kFWtwD.mjs.map +1 -0
  396. package/dist/{base64-C74Msx8h.mjs → base64-B-PsqheR.mjs} +8 -4
  397. package/dist/base64-B-PsqheR.mjs.map +1 -0
  398. package/dist/{byline-BWb5H-65.mjs → byline-XEjchwzZ.mjs} +6 -6
  399. package/dist/{byline-BWb5H-65.mjs.map → byline-XEjchwzZ.mjs.map} +1 -1
  400. package/dist/{byline-fields-DO6bAYK8.mjs → byline-fields-CdU_LTF1.mjs} +1 -1
  401. package/dist/{byline-fields-DO6bAYK8.mjs.map → byline-fields-CdU_LTF1.mjs.map} +1 -1
  402. package/dist/{bylines-BGuKFaBy.mjs → bylines-BJbT4gKS.mjs} +4 -4
  403. package/dist/{bylines-BGuKFaBy.mjs.map → bylines-BJbT4gKS.mjs.map} +1 -1
  404. package/dist/{bylines-CHojDMpj.mjs → bylines-czseViYo.mjs} +6 -6
  405. package/dist/{bylines-CHojDMpj.mjs.map → bylines-czseViYo.mjs.map} +1 -1
  406. package/dist/{cache-BeR7RfN8.mjs → cache-CGCd6AVM.mjs} +2 -2
  407. package/dist/{cache-BeR7RfN8.mjs.map → cache-CGCd6AVM.mjs.map} +1 -1
  408. package/dist/{challenge-store-B426wQ6O.mjs → challenge-store-BFzgFRog.mjs} +1 -1
  409. package/dist/{challenge-store-B426wQ6O.mjs.map → challenge-store-BFzgFRog.mjs.map} +1 -1
  410. package/dist/{chunks-BU5_nrAC.mjs → chunks-BxXyunY-.mjs} +2 -2
  411. package/dist/{chunks-BU5_nrAC.mjs.map → chunks-BxXyunY-.mjs.map} +1 -1
  412. package/dist/cli/index.mjs +138 -31
  413. package/dist/cli/index.mjs.map +1 -1
  414. package/dist/client/cf-access.d.mts +1 -1
  415. package/dist/client/index.d.mts +54 -3
  416. package/dist/client/index.d.mts.map +1 -1
  417. package/dist/client/index.mjs +16 -4
  418. package/dist/client/index.mjs.map +1 -1
  419. package/dist/{comment-SWh31kS0.mjs → comment-DPT0WKyd.mjs} +2 -2
  420. package/dist/{comment-SWh31kS0.mjs.map → comment-DPT0WKyd.mjs.map} +1 -1
  421. package/dist/{comment-reaction-DgcpziXI.mjs → comment-reaction-C65MldIB.mjs} +2 -2
  422. package/dist/{comment-reaction-DgcpziXI.mjs.map → comment-reaction-C65MldIB.mjs.map} +1 -1
  423. package/dist/{comments-kZYGppMS.mjs → comments-Bz6sCbgD.mjs} +3 -3
  424. package/dist/{comments-kZYGppMS.mjs.map → comments-Bz6sCbgD.mjs.map} +1 -1
  425. package/dist/{components-CliOkZAs.mjs → components-DZkL4Fx0.mjs} +1 -1
  426. package/dist/{components-CliOkZAs.mjs.map → components-DZkL4Fx0.mjs.map} +1 -1
  427. package/dist/{content-BO61Sx4j.mjs → content-Ci04z2z-.mjs} +53 -19
  428. package/dist/content-Ci04z2z-.mjs.map +1 -0
  429. package/dist/{content-refresh-BkkApzeS.mjs → content-refresh-D4khvC0R.mjs} +95 -3
  430. package/dist/content-refresh-D4khvC0R.mjs.map +1 -0
  431. package/dist/{context-DtSFYFuS.mjs → context-B6hc7zJL.mjs} +97 -74
  432. package/dist/context-B6hc7zJL.mjs.map +1 -0
  433. package/dist/{cron-YFJ7uNVA.mjs → cron-BlKIMD_e.mjs} +1 -1
  434. package/dist/{cron-YFJ7uNVA.mjs.map → cron-BlKIMD_e.mjs.map} +1 -1
  435. package/dist/{dashboard-xo_gPs_D.mjs → dashboard-C5NkXFbi.mjs} +4 -4
  436. package/dist/{dashboard-xo_gPs_D.mjs.map → dashboard-C5NkXFbi.mjs.map} +1 -1
  437. package/dist/db/index.d.mts +3 -3
  438. package/dist/db/index.mjs +2 -2
  439. package/dist/db/index.mjs.map +1 -1
  440. package/dist/db/libsql.d.mts +1 -1
  441. package/dist/db/postgres.d.mts +1 -1
  442. package/dist/db/sqlite.d.mts +1 -1
  443. package/dist/{db-errors-BKuXh_fi.mjs → db-errors-CcWLaRiR.mjs} +4 -2
  444. package/dist/db-errors-CcWLaRiR.mjs.map +1 -0
  445. package/dist/{default-DY5ovGUG.mjs → default-C4v9KP56.mjs} +1 -1
  446. package/dist/{default-DY5ovGUG.mjs.map → default-C4v9KP56.mjs.map} +1 -1
  447. package/dist/{device-flow-CPXQ_QfW.mjs → device-flow-BeA6lUS1.mjs} +5 -6
  448. package/dist/device-flow-BeA6lUS1.mjs.map +1 -0
  449. package/dist/{dialect-helpers-Cue7QQOR.mjs → dialect-helpers-HxEQGl_1.mjs} +57 -11
  450. package/dist/{dialect-helpers-Cue7QQOR.mjs.map → dialect-helpers-HxEQGl_1.mjs.map} +1 -1
  451. package/dist/{email-console-CjkQc7oY.mjs → email-console-C-9Ng8DM.mjs} +1 -1
  452. package/dist/{email-console-CjkQc7oY.mjs.map → email-console-C-9Ng8DM.mjs.map} +1 -1
  453. package/dist/error-CEGF6UZb.mjs +90 -0
  454. package/dist/error-CEGF6UZb.mjs.map +1 -0
  455. package/dist/{error-CfJMLSkd.mjs → errors-DtEXIQQV.mjs} +15 -84
  456. package/dist/errors-DtEXIQQV.mjs.map +1 -0
  457. package/dist/{escape-r8_GbEU2.mjs → escape-CFedIB1C.mjs} +1 -1
  458. package/dist/{escape-r8_GbEU2.mjs.map → escape-CFedIB1C.mjs.map} +1 -1
  459. package/dist/{field-defs-cache-BVrNCgSj.mjs → field-defs-cache-DvmlgP-D.mjs} +2 -2
  460. package/dist/{field-defs-cache-BVrNCgSj.mjs.map → field-defs-cache-DvmlgP-D.mjs.map} +1 -1
  461. package/dist/{fts-manager-BD9s77qh.mjs → fts-manager-DzqIBrrW.mjs} +3 -3
  462. package/dist/{fts-manager-BD9s77qh.mjs.map → fts-manager-DzqIBrrW.mjs.map} +1 -1
  463. package/dist/{hreflang-CgIxhH4k.d.mts → hreflang-CIxH_LKz.d.mts} +2 -2
  464. package/dist/{hreflang-CgIxhH4k.d.mts.map → hreflang-CIxH_LKz.d.mts.map} +1 -1
  465. package/dist/{hreflang-pAWUiPd_.mjs → hreflang-db2Tpb2o.mjs} +5 -5
  466. package/dist/{hreflang-pAWUiPd_.mjs.map → hreflang-db2Tpb2o.mjs.map} +1 -1
  467. package/dist/{import-2jHtLzsO.mjs → import-Dmkm8S1W.mjs} +4 -4
  468. package/dist/{import-2jHtLzsO.mjs.map → import-Dmkm8S1W.mjs.map} +1 -1
  469. package/dist/{index-DxjWLKGT.d.mts → index-BEui-cRd.d.mts} +3 -3
  470. package/dist/{index-DxjWLKGT.d.mts.map → index-BEui-cRd.d.mts.map} +1 -1
  471. package/dist/{index-BXOCyLj0.d.mts → index-wYnC6u4o.d.mts} +780 -15
  472. package/dist/index-wYnC6u4o.d.mts.map +1 -0
  473. package/dist/index.d.mts +18 -19
  474. package/dist/index.mjs +59 -58
  475. package/dist/{load-DgoWMdRX.mjs → load-Cx27ki1l.mjs} +2 -2
  476. package/dist/{load-DgoWMdRX.mjs.map → load-Cx27ki1l.mjs.map} +1 -1
  477. package/dist/{loader-Rb8QlGZp.mjs → loader-Be3ouI5L.mjs} +170 -6
  478. package/dist/loader-Be3ouI5L.mjs.map +1 -0
  479. package/dist/{manifest-schema-DeGNma2O.mjs → manifest-schema-bCq54i7F.mjs} +16 -2
  480. package/dist/manifest-schema-bCq54i7F.mjs.map +1 -0
  481. package/dist/media/image-endpoint.d.mts +30 -2
  482. package/dist/media/image-endpoint.d.mts.map +1 -1
  483. package/dist/media/image-endpoint.mjs +29 -3
  484. package/dist/media/image-endpoint.mjs.map +1 -1
  485. package/dist/media/index.d.mts +1 -1
  486. package/dist/media/local-runtime.d.mts +12 -13
  487. package/dist/media/local-runtime.d.mts.map +1 -1
  488. package/dist/media/local-runtime.mjs +6 -6
  489. package/dist/{media-D2ZFowpn.mjs → media-BjhhENaJ.mjs} +2 -2
  490. package/dist/{media-D2ZFowpn.mjs.map → media-BjhhENaJ.mjs.map} +1 -1
  491. package/dist/{media-allowlist-Dc05vKtt.mjs → media-allowlist-jUppPWFy.mjs} +1 -1
  492. package/dist/{media-allowlist-Dc05vKtt.mjs.map → media-allowlist-jUppPWFy.mjs.map} +1 -1
  493. package/dist/media-kIV1IxFf.mjs +243 -0
  494. package/dist/media-kIV1IxFf.mjs.map +1 -0
  495. package/dist/media-upload-D2wk3EIt.mjs +145 -0
  496. package/dist/media-upload-D2wk3EIt.mjs.map +1 -0
  497. package/dist/{media-url-DgIEYZr4.mjs → media-url-BCm5vBn6.mjs} +1 -1
  498. package/dist/{media-url-DgIEYZr4.mjs.map → media-url-BCm5vBn6.mjs.map} +1 -1
  499. package/dist/{media-usage-B__v9NMm.mjs → media-usage-CljdO1mc.mjs} +138 -5
  500. package/dist/{media-usage-B__v9NMm.mjs.map → media-usage-CljdO1mc.mjs.map} +1 -1
  501. package/dist/{menus-BtvH7Z7t.mjs → menus-BcyElFtI.mjs} +155 -12
  502. package/dist/menus-BcyElFtI.mjs.map +1 -0
  503. package/dist/{menus-BpfevlJD.mjs → menus-CZyG6rvx.mjs} +2 -2
  504. package/dist/{menus-BpfevlJD.mjs.map → menus-CZyG6rvx.mjs.map} +1 -1
  505. package/dist/{mode-BVnhGZ2o.mjs → mode-fiXRMfeA.mjs} +1 -1
  506. package/dist/{mode-BVnhGZ2o.mjs.map → mode-fiXRMfeA.mjs.map} +1 -1
  507. package/dist/{oauth-authorization-BWqWTHBK.mjs → oauth-authorization-oqfbBuMH.mjs} +5 -6
  508. package/dist/oauth-authorization-oqfbBuMH.mjs.map +1 -0
  509. package/dist/{oauth-clients-43_XADoa.mjs → oauth-clients-BlrUHAsf.mjs} +1 -1
  510. package/dist/{oauth-clients-43_XADoa.mjs.map → oauth-clients-BlrUHAsf.mjs.map} +1 -1
  511. package/dist/{oauth-state-store-BRSUEkmx.mjs → oauth-state-store-DnvjRMy6.mjs} +2 -1
  512. package/dist/{oauth-state-store-BRSUEkmx.mjs.map → oauth-state-store-DnvjRMy6.mjs.map} +1 -1
  513. package/dist/{oauth-user-lookup-Ctgemb0k.mjs → oauth-user-lookup-oUllxvAv.mjs} +1 -1
  514. package/dist/{oauth-user-lookup-Ctgemb0k.mjs.map → oauth-user-lookup-oUllxvAv.mjs.map} +1 -1
  515. package/dist/object-cache/memory.d.mts +1 -1
  516. package/dist/{options-COXGTtqd.d.mts → options-BqMkWfYa.d.mts} +6 -6
  517. package/dist/{options-COXGTtqd.d.mts.map → options-BqMkWfYa.d.mts.map} +1 -1
  518. package/dist/page/index.d.mts +2 -2
  519. package/dist/page/index.d.mts.map +1 -1
  520. package/dist/page/index.mjs.map +1 -1
  521. package/dist/{parse-GLksnn-B.mjs → parse-C_-6klII.mjs} +4 -12
  522. package/dist/{parse-GLksnn-B.mjs.map → parse-C_-6klII.mjs.map} +1 -1
  523. package/dist/{passkey-config-CjA1UbDj.mjs → passkey-config-C-SKpc0-.mjs} +1 -1
  524. package/dist/{passkey-config-CjA1UbDj.mjs.map → passkey-config-C-SKpc0-.mjs.map} +1 -1
  525. package/dist/{placeholder-DR-eeZr-.d.mts → placeholder-Dop47JMM.d.mts} +1 -1
  526. package/dist/{placeholder-DR-eeZr-.d.mts.map → placeholder-Dop47JMM.d.mts.map} +1 -1
  527. package/dist/plugin-types.d.mts +21 -2
  528. package/dist/plugin-types.d.mts.map +1 -1
  529. package/dist/plugin-utils.d.mts +18 -17
  530. package/dist/plugin-utils.d.mts.map +1 -1
  531. package/dist/plugin-utils.mjs +8 -6
  532. package/dist/plugin-utils.mjs.map +1 -1
  533. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -11
  534. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  535. package/dist/plugins/adapt-sandbox-entry.mjs +15 -3
  536. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  537. package/dist/{portable-text-BAkgEKcv.mjs → portable-text-sDj92Kxv.mjs} +1 -1
  538. package/dist/{portable-text-BAkgEKcv.mjs.map → portable-text-sDj92Kxv.mjs.map} +1 -1
  539. package/dist/{preview-Rloa09Wp.mjs → preview-D4Jnbfx7.mjs} +2 -2
  540. package/dist/{preview-Rloa09Wp.mjs.map → preview-D4Jnbfx7.mjs.map} +1 -1
  541. package/dist/{public-url-C0cEvrRu.mjs → public-url-DSGTnJFw.mjs} +5 -3
  542. package/dist/public-url-DSGTnJFw.mjs.map +1 -0
  543. package/dist/{query-ZzvXyxWq.mjs → query-DR73ZNfm.mjs} +14 -14
  544. package/dist/{query-ZzvXyxWq.mjs.map → query-DR73ZNfm.mjs.map} +1 -1
  545. package/dist/{query-BxT95opY.mjs → query-Di7DOmPV.mjs} +3 -3
  546. package/dist/{query-BxT95opY.mjs.map → query-Di7DOmPV.mjs.map} +1 -1
  547. package/dist/{rate-limit-Cg4Rvc_-.mjs → rate-limit-CMj83JNW.mjs} +2 -2
  548. package/dist/{rate-limit-Cg4Rvc_-.mjs.map → rate-limit-CMj83JNW.mjs.map} +1 -1
  549. package/dist/{redirect-Ce_6Yb6z.mjs → redirect-Botom7X6.mjs} +1 -1
  550. package/dist/{redirect-Ce_6Yb6z.mjs.map → redirect-Botom7X6.mjs.map} +1 -1
  551. package/dist/{redirect-Ch3E98qZ.mjs → redirect-CgLPYflR.mjs} +10 -4
  552. package/dist/redirect-CgLPYflR.mjs.map +1 -0
  553. package/dist/{redirects-CXmVGRNZ.mjs → redirects-B7t9bKsL.mjs} +4 -4
  554. package/dist/{redirects-CXmVGRNZ.mjs.map → redirects-B7t9bKsL.mjs.map} +1 -1
  555. package/dist/{redirects-CeD7K7EX.mjs → redirects-BBq3n2Rj.mjs} +4 -175
  556. package/dist/redirects-BBq3n2Rj.mjs.map +1 -0
  557. package/dist/{registry-CyIXPi4E.mjs → registry-FV15nLge.mjs} +97 -8
  558. package/dist/registry-FV15nLge.mjs.map +1 -0
  559. package/dist/{relation-Ba68Tm4Z.mjs → relation-9M9j_UWo.mjs} +3 -3
  560. package/dist/{relation-Ba68Tm4Z.mjs.map → relation-9M9j_UWo.mjs.map} +1 -1
  561. package/dist/{relations-BmBmX4Eb.mjs → relations-5_avdrN_.mjs} +2 -2
  562. package/dist/{relations-BmBmX4Eb.mjs.map → relations-5_avdrN_.mjs.map} +1 -1
  563. package/dist/{relations-C8hVyEy0.mjs → relations-BKvO39-9.mjs} +5 -5
  564. package/dist/{relations-C8hVyEy0.mjs.map → relations-BKvO39-9.mjs.map} +1 -1
  565. package/dist/{relations-CbBpoOys.d.mts → relations-Bh8jlFsU.d.mts} +306 -4
  566. package/dist/relations-Bh8jlFsU.d.mts.map +1 -0
  567. package/dist/{request-meta-DCwktC5P.mjs → request-meta-DzXYYI-n.mjs} +2 -2
  568. package/dist/{request-meta-DCwktC5P.mjs.map → request-meta-DzXYYI-n.mjs.map} +1 -1
  569. package/dist/{runner-CiNdIk3y.d.mts → runner-Dd8m8k9F.d.mts} +2 -2
  570. package/dist/{runner-CiNdIk3y.d.mts.map → runner-Dd8m8k9F.d.mts.map} +1 -1
  571. package/dist/{runner-jt65bF7F.mjs → runner-DfnZ5eUr.mjs} +333 -199
  572. package/dist/runner-DfnZ5eUr.mjs.map +1 -0
  573. package/dist/runtime.d.mts +11 -12
  574. package/dist/runtime.d.mts.map +1 -1
  575. package/dist/runtime.mjs +4 -4
  576. package/dist/{schema-DwjufZ1S.mjs → schema-BXxlHeAf.mjs} +3 -3
  577. package/dist/{schema-DwjufZ1S.mjs.map → schema-BXxlHeAf.mjs.map} +1 -1
  578. package/dist/scopes-Bl4IwHA-.mjs +27 -0
  579. package/dist/scopes-Bl4IwHA-.mjs.map +1 -0
  580. package/dist/{search-Cl875CVE.mjs → search-Bff-7jFt.mjs} +5 -5
  581. package/dist/{search-Cl875CVE.mjs.map → search-Bff-7jFt.mjs.map} +1 -1
  582. package/dist/{secrets-CO_bEmII.mjs → secrets-CSwQIl4q.mjs} +3 -3
  583. package/dist/{secrets-CO_bEmII.mjs.map → secrets-CSwQIl4q.mjs.map} +1 -1
  584. package/dist/{sections-DTKzgoiS.mjs → sections-CwW4s1al.mjs} +3 -3
  585. package/dist/{sections-DTKzgoiS.mjs.map → sections-CwW4s1al.mjs.map} +1 -1
  586. package/dist/seed/index.d.mts +2 -2
  587. package/dist/seed/index.mjs +20 -20
  588. package/dist/seo/index.d.mts +14 -3
  589. package/dist/seo/index.d.mts.map +1 -1
  590. package/dist/seo/index.mjs +16 -16
  591. package/dist/seo/index.mjs.map +1 -1
  592. package/dist/{seo-BLm6SXzS.mjs → seo-2cmFHZns.mjs} +2 -2
  593. package/dist/{seo-BLm6SXzS.mjs.map → seo-2cmFHZns.mjs.map} +1 -1
  594. package/dist/{seo-BjrbXAd_.mjs → seo-C3GDfT0V.mjs} +1 -1
  595. package/dist/{seo-BjrbXAd_.mjs.map → seo-C3GDfT0V.mjs.map} +1 -1
  596. package/dist/{service-D6zDmKXy.mjs → service-DkGTWGIi.mjs} +3 -3
  597. package/dist/{service-D6zDmKXy.mjs.map → service-DkGTWGIi.mjs.map} +1 -1
  598. package/dist/{session-user-DoSs6YM8.mjs → session-user-DbHqKDKe.mjs} +1 -1
  599. package/dist/{session-user-DoSs6YM8.mjs.map → session-user-DbHqKDKe.mjs.map} +1 -1
  600. package/dist/{settings-CmS3TwXf.mjs → settings-C4s8hFQm.mjs} +3 -3
  601. package/dist/{settings-CmS3TwXf.mjs.map → settings-C4s8hFQm.mjs.map} +1 -1
  602. package/dist/{settings-BuQYvLfy.mjs → settings-CpA4lQFt.mjs} +4 -4
  603. package/dist/{settings-BuQYvLfy.mjs.map → settings-CpA4lQFt.mjs.map} +1 -1
  604. package/dist/{setup-complete-BPNgLHlU.mjs → setup-complete-CdIlqloh.mjs} +1 -1
  605. package/dist/{setup-complete-BPNgLHlU.mjs.map → setup-complete-CdIlqloh.mjs.map} +1 -1
  606. package/dist/{setup-nonce-DEPZX96T.mjs → setup-nonce-DqiIPr-J.mjs} +1 -1
  607. package/dist/{setup-nonce-DEPZX96T.mjs.map → setup-nonce-DqiIPr-J.mjs.map} +1 -1
  608. package/dist/{single-flight-cache-DEtW-OEG.mjs → single-flight-cache-C2exrGAi.mjs} +1 -1
  609. package/dist/{single-flight-cache-DEtW-OEG.mjs.map → single-flight-cache-C2exrGAi.mjs.map} +1 -1
  610. package/dist/{site-url-CcLy5D5s.mjs → site-url-NGJT6NTU.mjs} +1 -1
  611. package/dist/{site-url-CcLy5D5s.mjs.map → site-url-NGJT6NTU.mjs.map} +1 -1
  612. package/dist/snapshot-BdpUJKD-.mjs +216 -0
  613. package/dist/snapshot-BdpUJKD-.mjs.map +1 -0
  614. package/dist/{ssrf-TrKYJAy0.mjs → ssrf-CviKqWmq.mjs} +1 -1
  615. package/dist/{ssrf-TrKYJAy0.mjs.map → ssrf-CviKqWmq.mjs.map} +1 -1
  616. package/dist/state-xxv6ZTMv.mjs +128 -0
  617. package/dist/state-xxv6ZTMv.mjs.map +1 -0
  618. package/dist/{status-D3Gy1Zc6.mjs → status-COfycGIU.mjs} +1 -1
  619. package/dist/{status-D3Gy1Zc6.mjs.map → status-COfycGIU.mjs.map} +1 -1
  620. package/dist/storage/local.d.mts +1 -1
  621. package/dist/storage/local.mjs +1 -1
  622. package/dist/storage/s3.d.mts +1 -1
  623. package/dist/storage/s3.mjs +1 -1
  624. package/dist/{taxonomies-ClS4RKaZ.mjs → taxonomies-Ce49uIzY.mjs} +4 -4
  625. package/dist/{taxonomies-ClS4RKaZ.mjs.map → taxonomies-Ce49uIzY.mjs.map} +1 -1
  626. package/dist/{taxonomies-pUkuD6nj.mjs → taxonomies-DjSKBZpq.mjs} +7 -7
  627. package/dist/{taxonomies-pUkuD6nj.mjs.map → taxonomies-DjSKBZpq.mjs.map} +1 -1
  628. package/dist/{taxonomy-BkxbUMSw.mjs → taxonomy-DfVooU4W.mjs} +51 -13
  629. package/dist/taxonomy-DfVooU4W.mjs.map +1 -0
  630. package/dist/{tokens-BV6fgl5A.mjs → tokens-DVltpO2D.mjs} +2 -2
  631. package/dist/{tokens-BV6fgl5A.mjs.map → tokens-DVltpO2D.mjs.map} +1 -1
  632. package/dist/{transport-DTFhhPOg.mjs → transport-CKrlqSpD.mjs} +1 -1
  633. package/dist/{transport-DTFhhPOg.mjs.map → transport-CKrlqSpD.mjs.map} +1 -1
  634. package/dist/{transport-DmlmzD2U.d.mts → transport-Vd7VdqHv.d.mts} +1 -1
  635. package/dist/{transport-DmlmzD2U.d.mts.map → transport-Vd7VdqHv.d.mts.map} +1 -1
  636. package/dist/{trusted-proxy-CSgZVESw.mjs → trusted-proxy-CwjQj0YG.mjs} +1 -1
  637. package/dist/{trusted-proxy-CSgZVESw.mjs.map → trusted-proxy-CwjQj0YG.mjs.map} +1 -1
  638. package/dist/{types-B6-N9cBB.d.mts → types-B4vm81VH.d.mts} +1 -1
  639. package/dist/{types-B6-N9cBB.d.mts.map → types-B4vm81VH.d.mts.map} +1 -1
  640. package/dist/{types-Cm4Cz-8M.d.mts → types-B97jg3aW.d.mts} +6 -1
  641. package/dist/{types-Cm4Cz-8M.d.mts.map → types-B97jg3aW.d.mts.map} +1 -1
  642. package/dist/{types-BNZsPL9z.mjs → types-BD40g7zz.mjs} +1 -1
  643. package/dist/{types-BNZsPL9z.mjs.map → types-BD40g7zz.mjs.map} +1 -1
  644. package/dist/{types-QnlQIyPE.d.mts → types-BNfPHfay.d.mts} +1 -1
  645. package/dist/{types-QnlQIyPE.d.mts.map → types-BNfPHfay.d.mts.map} +1 -1
  646. package/dist/{types-DLIrNtv5.d.mts → types-BfYUPJdP.d.mts} +2 -2
  647. package/dist/{types-DLIrNtv5.d.mts.map → types-BfYUPJdP.d.mts.map} +1 -1
  648. package/dist/{types-CtDVagM4.d.mts → types-BvB7gDOD.d.mts} +38 -4
  649. package/dist/types-BvB7gDOD.d.mts.map +1 -0
  650. package/dist/{types-B_pmP7Yq.d.mts → types-C7U0HlEr.d.mts} +1 -1
  651. package/dist/{types-B_pmP7Yq.d.mts.map → types-C7U0HlEr.d.mts.map} +1 -1
  652. package/dist/{types-Ch8pFMEm.mjs → types-D1iJ3DpO.mjs} +3 -3
  653. package/dist/{types-Ch8pFMEm.mjs.map → types-D1iJ3DpO.mjs.map} +1 -1
  654. package/dist/{types-momGysZk.d.mts → types-DPKhI5ib.d.mts} +9 -1
  655. package/dist/types-DPKhI5ib.d.mts.map +1 -0
  656. package/dist/{types-BCVuUPrL.d.mts → types-Kmaxdarz.d.mts} +1 -1
  657. package/dist/{types-BCVuUPrL.d.mts.map → types-Kmaxdarz.d.mts.map} +1 -1
  658. package/dist/{types-qljd1hsL.d.mts → types-iVeZ5g6N.d.mts} +1 -1
  659. package/dist/{types-qljd1hsL.d.mts.map → types-iVeZ5g6N.d.mts.map} +1 -1
  660. package/dist/{user-DTxX_Fha.mjs → user-Bh-L1qo6.mjs} +3 -3
  661. package/dist/{user-DTxX_Fha.mjs.map → user-Bh-L1qo6.mjs.map} +1 -1
  662. package/dist/{utils-Cz8PVrl4.mjs → utils-BbUgmeZG.mjs} +1 -1
  663. package/dist/{utils-Cz8PVrl4.mjs.map → utils-BbUgmeZG.mjs.map} +1 -1
  664. package/dist/{validate-9jLuE301.d.mts → validate-DULfgpJP.d.mts} +5 -5
  665. package/dist/{validate-9jLuE301.d.mts.map → validate-DULfgpJP.d.mts.map} +1 -1
  666. package/dist/{validate-C3CpWlNt.mjs → validate-V9nCwq_-.mjs} +2 -2
  667. package/dist/{validate-C3CpWlNt.mjs.map → validate-V9nCwq_-.mjs.map} +1 -1
  668. package/dist/{validation-DM2K_NJC.mjs → validation-BsVUJfsP.mjs} +10 -10
  669. package/dist/{validation-DM2K_NJC.mjs.map → validation-BsVUJfsP.mjs.map} +1 -1
  670. package/dist/version-907opKac.mjs +7 -0
  671. package/dist/{version-BlIJvNAZ.mjs.map → version-907opKac.mjs.map} +1 -1
  672. package/dist/{widgets-B_X6_UXJ.mjs → widgets-DGv1Z04V.mjs} +3 -3
  673. package/dist/{widgets-B_X6_UXJ.mjs.map → widgets-DGv1Z04V.mjs.map} +1 -1
  674. package/dist/{wxr-taxonomies-tADW4KTa.d.mts → wxr-taxonomies-B4tMuN5T.d.mts} +1 -1
  675. package/dist/{wxr-taxonomies-tADW4KTa.d.mts.map → wxr-taxonomies-B4tMuN5T.d.mts.map} +1 -1
  676. package/dist/{wxr-taxonomies-oNJB2S81.mjs → wxr-taxonomies-Cs1N6O8z.mjs} +3 -3
  677. package/dist/{wxr-taxonomies-oNJB2S81.mjs.map → wxr-taxonomies-Cs1N6O8z.mjs.map} +1 -1
  678. package/dist/{zod-generator-CrfV79De.mjs → zod-generator-B5prQ5M4.mjs} +2 -2
  679. package/dist/{zod-generator-CrfV79De.mjs.map → zod-generator-B5prQ5M4.mjs.map} +1 -1
  680. package/package.json +8 -8
  681. package/src/api/authorize.ts +10 -1
  682. package/src/api/error.ts +5 -5
  683. package/src/api/errors.ts +14 -0
  684. package/src/api/handlers/backup.ts +312 -0
  685. package/src/api/handlers/content.ts +36 -2
  686. package/src/api/handlers/device-flow.ts +2 -3
  687. package/src/api/handlers/index.ts +16 -0
  688. package/src/api/handlers/marketplace.ts +38 -0
  689. package/src/api/handlers/media-upload.ts +210 -0
  690. package/src/api/handlers/media-usage.ts +211 -2
  691. package/src/api/handlers/oauth-authorization.ts +2 -6
  692. package/src/api/handlers/plugin-settings.ts +218 -0
  693. package/src/api/handlers/plugins.ts +41 -0
  694. package/src/api/handlers/registry.ts +39 -0
  695. package/src/api/handlers/snapshot.ts +21 -3
  696. package/src/api/openapi/document.ts +48 -6
  697. package/src/api/parse.ts +1 -15
  698. package/src/api/public-url.ts +4 -2
  699. package/src/api/schemas/common.ts +6 -2
  700. package/src/api/schemas/media-usage.ts +72 -0
  701. package/src/api/schemas/media.ts +32 -1
  702. package/src/astro/integration/index.ts +38 -0
  703. package/src/astro/integration/routes.ts +35 -0
  704. package/src/astro/integration/runtime.ts +6 -0
  705. package/src/astro/integration/virtual-modules.ts +41 -7
  706. package/src/astro/integration/vite-config.ts +17 -1
  707. package/src/astro/middleware/auth.ts +70 -78
  708. package/src/astro/middleware/csp.ts +5 -2
  709. package/src/astro/middleware.ts +86 -31
  710. package/src/astro/public-plugin-api-routes.ts +2 -1
  711. package/src/astro/routes/PluginRegistry.tsx +5 -2
  712. package/src/astro/routes/admin.astro +56 -16
  713. package/src/astro/routes/api/admin/api-tokens/index.ts +2 -2
  714. package/src/astro/routes/api/admin/bylines/[id]/translations.ts +2 -5
  715. package/src/astro/routes/api/admin/plugins/[id]/index.ts +6 -0
  716. package/src/astro/routes/api/admin/plugins/[id]/mcp.ts +42 -0
  717. package/src/astro/routes/api/admin/plugins/[id]/settings.ts +80 -0
  718. package/src/astro/routes/api/admin/plugins/[id]/update.ts +2 -0
  719. package/src/astro/routes/api/admin/plugins/index.ts +9 -0
  720. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +2 -0
  721. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +2 -0
  722. package/src/astro/routes/api/admin/plugins/registry/install.ts +2 -0
  723. package/src/astro/routes/api/admin/plugins/updates.ts +2 -4
  724. package/src/astro/routes/api/auth/mode.ts +6 -14
  725. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +21 -4
  726. package/src/astro/routes/api/auth/oauth/[provider].ts +24 -6
  727. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +3 -4
  728. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +2 -3
  729. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +2 -3
  730. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +3 -4
  731. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts +4 -6
  732. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/parents.ts +2 -3
  733. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -4
  734. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +4 -6
  735. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +3 -4
  736. package/src/astro/routes/api/content/[collection]/[id].ts +3 -4
  737. package/src/astro/routes/api/content/[collection]/authors.ts +2 -3
  738. package/src/astro/routes/api/content/[collection]/index.ts +6 -8
  739. package/src/astro/routes/api/content/[collection]/trash.ts +2 -3
  740. package/src/astro/routes/api/manifest.ts +2 -9
  741. package/src/astro/routes/api/mcp.ts +2 -1
  742. package/src/astro/routes/api/media/[id]/usage.ts +30 -0
  743. package/src/astro/routes/api/media/[id].ts +17 -6
  744. package/src/astro/routes/api/media/file/[...key].ts +8 -0
  745. package/src/astro/routes/api/media.ts +21 -2
  746. package/src/astro/routes/api/oauth/authorize.ts +2 -6
  747. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +17 -3
  748. package/src/astro/routes/api/settings/backups/archives/[name].ts +78 -0
  749. package/src/astro/routes/api/settings/backups/archives/index.ts +33 -0
  750. package/src/astro/routes/api/settings/backups/export.ts +43 -0
  751. package/src/astro/routes/api/settings/backups/index.ts +76 -0
  752. package/src/astro/routes/api/setup/admin-verify.ts +8 -4
  753. package/src/astro/routes/api/setup/admin.ts +11 -4
  754. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +4 -1
  755. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +5 -1
  756. package/src/astro/types.ts +48 -2
  757. package/src/auth/api-tokens.ts +1 -0
  758. package/src/auth/oauth-state-store.ts +3 -0
  759. package/src/auth/providers/github-admin.tsx +5 -6
  760. package/src/auth/providers/google-admin.tsx +5 -6
  761. package/src/auth/scopes.ts +3 -9
  762. package/src/cli/client-factory.ts +0 -1
  763. package/src/cli/commands/bundle-utils.ts +50 -1
  764. package/src/cli/commands/bundle.ts +9 -2
  765. package/src/cli/commands/media.ts +127 -0
  766. package/src/client/index.ts +84 -2
  767. package/src/client/transport.ts +1 -1
  768. package/src/components/EmDashHead.astro +4 -8
  769. package/src/components/InlinePortableTextEditor.tsx +56 -35
  770. package/src/components/marks/Link.astro +5 -1
  771. package/src/database/dialect-helpers.ts +77 -3
  772. package/src/database/migrations/051_content_taxonomies_denorm.ts +120 -0
  773. package/src/database/migrations/052_media_usage_read_index.ts +26 -0
  774. package/src/database/migrations/053_plugin_mcp_tools.ts +21 -0
  775. package/src/database/migrations/runner.ts +6 -0
  776. package/src/database/repositories/audit.ts +2 -1
  777. package/src/database/repositories/content.ts +98 -16
  778. package/src/database/repositories/media-usage.ts +250 -1
  779. package/src/database/repositories/plugin-storage.ts +29 -32
  780. package/src/database/repositories/redirect.ts +14 -2
  781. package/src/database/repositories/taxonomy.ts +57 -8
  782. package/src/database/types.ts +23 -0
  783. package/src/db/adapters.ts +9 -0
  784. package/src/emdash-runtime.ts +310 -66
  785. package/src/loader.ts +292 -1
  786. package/src/mcp/server.ts +222 -5
  787. package/src/media/image-endpoint.ts +38 -2
  788. package/src/page/metadata.ts +27 -0
  789. package/src/plugin-types.ts +19 -0
  790. package/src/plugin-utils.ts +8 -6
  791. package/src/plugins/adapt-sandbox-entry.ts +30 -1
  792. package/src/plugins/context.ts +7 -0
  793. package/src/plugins/define-plugin.ts +16 -0
  794. package/src/plugins/manifest-schema.ts +28 -1
  795. package/src/plugins/routes.ts +27 -1
  796. package/src/plugins/sandbox/runner-options.ts +16 -0
  797. package/src/plugins/sandbox/types.ts +6 -1
  798. package/src/plugins/state.ts +17 -1
  799. package/src/plugins/storage-query.ts +67 -33
  800. package/src/plugins/types.ts +41 -0
  801. package/src/schema/registry.ts +141 -5
  802. package/src/seed/apply.ts +28 -29
  803. package/src/seo/index.ts +19 -4
  804. package/src/storage/types.ts +6 -0
  805. package/src/utils/base64.ts +7 -2
  806. package/src/utils/db-errors.ts +3 -1
  807. package/src/virtual-modules.d.ts +13 -0
  808. package/src/visual-editing/toolbar.ts +1 -1
  809. package/dist/adapters-DPAkL6QF.d.mts.map +0 -1
  810. package/dist/api-DVbHNApo.mjs.map +0 -1
  811. package/dist/api-tokens-Dk2GrJMY.mjs +0 -3
  812. package/dist/apply-D23iUV4q.mjs.map +0 -1
  813. package/dist/authorize-BmCwQCib.mjs.map +0 -1
  814. package/dist/base64-C74Msx8h.mjs.map +0 -1
  815. package/dist/content-BO61Sx4j.mjs.map +0 -1
  816. package/dist/content-refresh-BkkApzeS.mjs.map +0 -1
  817. package/dist/context-DtSFYFuS.mjs.map +0 -1
  818. package/dist/db-errors-BKuXh_fi.mjs.map +0 -1
  819. package/dist/device-flow-CPXQ_QfW.mjs.map +0 -1
  820. package/dist/error-CfJMLSkd.mjs.map +0 -1
  821. package/dist/index-BXOCyLj0.d.mts.map +0 -1
  822. package/dist/loader-Rb8QlGZp.mjs.map +0 -1
  823. package/dist/manifest-schema-DeGNma2O.mjs.map +0 -1
  824. package/dist/menus-BtvH7Z7t.mjs.map +0 -1
  825. package/dist/oauth-authorization-BWqWTHBK.mjs.map +0 -1
  826. package/dist/public-url-C0cEvrRu.mjs.map +0 -1
  827. package/dist/redirect-Ch3E98qZ.mjs.map +0 -1
  828. package/dist/redirects-CeD7K7EX.mjs.map +0 -1
  829. package/dist/registry-CyIXPi4E.mjs.map +0 -1
  830. package/dist/relations-CbBpoOys.d.mts.map +0 -1
  831. package/dist/runner-jt65bF7F.mjs.map +0 -1
  832. package/dist/scheduled-publish-DlJHGcuL.d.mts +0 -11
  833. package/dist/scheduled-publish-DlJHGcuL.d.mts.map +0 -1
  834. package/dist/taxonomy-BkxbUMSw.mjs.map +0 -1
  835. package/dist/types-CtDVagM4.d.mts.map +0 -1
  836. package/dist/types-momGysZk.d.mts.map +0 -1
  837. package/dist/version-BlIJvNAZ.mjs +0 -7
  838. /package/dist/{ssrf-B5DOv5E5.mjs → ssrf-BB__xfNM.mjs} +0 -0
  839. /package/dist/{types-DeCZBZxn.mjs → types-CK0LxPUQ.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"single-flight-cache-DEtW-OEG.mjs","names":[],"sources":["../src/utils/single-flight-cache.ts"],"sourcesContent":["/**\n * Global-scope async value cache with single-flight and poison-immunity.\n *\n * Built for the \"compute once for the lifetime of the JS global scope, read\n * on every request\" caches (site settings, search-health verification, ...).\n * That global scope is the process on Node and the isolate on Cloudflare\n * Workers — this helper is platform-neutral; the hazard it defends against is\n * specific to workerd but the cache itself is not.\n *\n * These caches must coalesce concurrent cold reads into one query — but the\n * obvious way to do that, caching the in-flight *promise* on a global and\n * awaiting it from later requests, is unsafe on workerd: if the request that\n * created the promise is cancelled mid-await (client disconnect, context\n * teardown), its continuation never runs, so the promise neither resolves nor\n * rejects. Every later request that awaits that shared promise then hangs\n * until the isolate is evicted (observed as 524s at the 100s wall, near-zero\n * CPU). A `.catch`/`.finally` that clears the cache doesn't help — a cancelled\n * request settles neither way.\n *\n * This cache stores the resolved *value* (not a promise) and coalesces via\n * `initWithLock`: one request becomes the owner and runs `fetch`, everyone\n * else polls for the published value and never awaits the owner's promise.\n * A cancelled owner can therefore never strand a waiter — the worst case is\n * the lock looks held until `deadlineMs`, then the next caller reclaims. The\n * owner's `fetch` is also anchored (waitUntil) so a cancelled originator's\n * query still completes and populates the cache, and bounded by\n * `ownerTimeoutMs` so a genuinely stuck fetch reclaims instead of hanging.\n *\n * Invalidation bumps `version`; reads compare against the version captured at\n * call time and refetch on mismatch.\n */\n\nimport { createInitLock, type InitLock, initWithLock } from \"./init-lock.js\";\n\nexport interface SingleFlightCache<T> {\n\t/** Last resolved value, valid only when `hasValue` is true. */\n\tvalue: T | null;\n\t/**\n\t * Presence flag, separate from `value` so that falsy/`undefined`/`void`\n\t * results cache correctly (a plain null check can't distinguish \"cached\n\t * undefined\" from \"never fetched\").\n\t */\n\thasValue: boolean;\n\t/** Invalidation counter; bumped by `invalidateSingleFlightCache`. */\n\tversion: number;\n\t/** The `version` the cached value was fetched at. */\n\tvalueVersion: number;\n\t/** Reclaimable single-flight lock (see init-lock.ts). */\n\tlock: InitLock;\n}\n\nexport function createSingleFlightCache<T>(): SingleFlightCache<T> {\n\treturn { value: null, hasValue: false, version: 0, valueVersion: -1, lock: createInitLock() };\n}\n\n/**\n * Force the next `singleFlightCached` call to refetch. An in-flight owner\n * fetched at the old version will not publish into the new version, so its\n * result is ignored by subsequent reads.\n */\nexport function invalidateSingleFlightCache(cache: SingleFlightCache<unknown>): void {\n\tcache.version++;\n\tcache.hasValue = false;\n\tcache.value = null;\n\tcache.valueVersion = -1;\n\t// Free the single-flight lock so a reader at the new version starts the\n\t// refetch immediately instead of waiting out a stale owner's deadline. A\n\t// still-running old-version owner can neither publish into the new version\n\t// (version gate) nor clobber a new owner (claim gate), so releasing here\n\t// is safe; the worst case is one brief duplicate fetch.\n\tcache.lock.ownerStartedAt = null;\n}\n\n/**\n * Headroom between the owner's own timeout and the waiter reclaim deadline.\n * The reclaim deadline must sit *above* `ownerTimeoutMs` so a slow-but-live\n * owner times out (and releases the lock) before a waiter would reclaim it —\n * otherwise a fetch slower than the deadline is superseded before it can\n * publish, and steady traffic turns that into a self-sustaining stampede.\n */\nconst RECLAIM_HEADROOM_MS = 5_000;\n\nexport interface SingleFlightCachedOptions {\n\t/**\n\t * Hand the in-flight fetch to the host's lifetime extender (waitUntil via\n\t * `after()`), so a cancelled originating request still drives it to\n\t * completion and populates the cache.\n\t */\n\tanchor?: (promise: Promise<void>) => void;\n\t/** Reclaim the single-flight lock if the owner holds it past this. */\n\tdeadlineMs?: number;\n\t/** Waiter poll interval. */\n\tpollMs?: number;\n\t/** Waiter gives up and throws after this long rather than hanging. */\n\tmaxWaitMs?: number;\n\t/**\n\t * Bound the owner's own `fetch`: if it doesn't settle within this, the\n\t * owner rejects (and releases the lock) instead of waiting indefinitely.\n\t * The anchored copy keeps running, so a slow-but-live fetch can still\n\t * publish for a later caller. Omit to leave the owner unbounded.\n\t */\n\townerTimeoutMs?: number;\n}\n\n/** Boxed cache hit so a `void`/falsy value is still distinguishable from a miss. */\ninterface Box<T> {\n\tv: T;\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\treject(new Error(`singleFlightCached: owner fetch exceeded ${ms}ms`));\n\t\t}, ms);\n\t\t// Settle from the underlying promise (whichever wins the race with the\n\t\t// timer), and always clear the timer so a resolved fetch doesn't leave\n\t\t// a pending timeout holding the isolate alive.\n\t\tpromise.then(resolve, reject).finally(() => {\n\t\t\tclearTimeout(timer);\n\t\t});\n\t});\n}\n\n/**\n * Return the cached value for `cache`, computing it via `fetch` under a\n * single-flight lock on a miss. Concurrent callers coalesce onto one fetch;\n * a cancelled owner cannot poison later callers (see file header).\n */\nexport function singleFlightCached<T>(\n\tcache: SingleFlightCache<T>,\n\tfetch: () => Promise<T>,\n\toptions: SingleFlightCachedOptions = {},\n): Promise<T> {\n\t// Capture the version once: a value published at this version satisfies\n\t// this call; an invalidation that lands mid-fetch makes the published\n\t// value stale for *later* calls (which captured the newer version) but\n\t// still valid for this one.\n\tconst versionAtCall = cache.version;\n\n\t// Ignore a non-positive / non-finite owner timeout rather than letting it\n\t// degenerate into an instant-reject (setTimeout coerces NaN/0 to ~0ms).\n\tconst ownerTimeoutMs =\n\t\toptions.ownerTimeoutMs !== undefined &&\n\t\tNumber.isFinite(options.ownerTimeoutMs) &&\n\t\toptions.ownerTimeoutMs > 0\n\t\t\t? options.ownerTimeoutMs\n\t\t\t: undefined;\n\n\t// Keep the reclaim deadline above the owner timeout (see RECLAIM_HEADROOM_MS):\n\t// the owner's own timeout, not a waiter reclaim, is the primary release.\n\tconst deadlineMs =\n\t\townerTimeoutMs === undefined\n\t\t\t? options.deadlineMs\n\t\t\t: Math.max(options.deadlineMs ?? 0, ownerTimeoutMs + RECLAIM_HEADROOM_MS);\n\n\treturn initWithLock<Box<T>>(\n\t\tcache.lock,\n\t\t() =>\n\t\t\tcache.hasValue && cache.valueVersion === versionAtCall\n\t\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- hasValue gates that `value` holds a real T\n\t\t\t\t\t({ v: cache.value as T } satisfies Box<T>)\n\t\t\t\t: null,\n\t\t(isCurrentClaim) => {\n\t\t\t// The real work, anchored independently so a cancelled owner's\n\t\t\t// fetch still settles and publishes. Publication is gated on the\n\t\t\t// claim so a reclaimed slow owner can't clobber the reclaimer's\n\t\t\t// value (same contract as initWithLock's own callers).\n\t\t\tconst real = (async (): Promise<Box<T>> => {\n\t\t\t\tconst value = await fetch();\n\t\t\t\tif (isCurrentClaim()) {\n\t\t\t\t\tcache.value = value;\n\t\t\t\t\tcache.hasValue = true;\n\t\t\t\t\tcache.valueVersion = versionAtCall;\n\t\t\t\t}\n\t\t\t\treturn { v: value };\n\t\t\t})();\n\t\t\t// Anchor the real fetch (not the timeout race): this is what must\n\t\t\t// survive a cancelled owner and run to publication. initWithLock is\n\t\t\t// left to manage only the lock; we don't double-anchor.\n\t\t\toptions.anchor?.(\n\t\t\t\treal.then(\n\t\t\t\t\t() => undefined,\n\t\t\t\t\t() => undefined,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ownerTimeoutMs === undefined ? real : withTimeout(real, ownerTimeoutMs);\n\t\t},\n\t\t{\n\t\t\tdeadlineMs,\n\t\t\tpollMs: options.pollMs,\n\t\t\tmaxWaitMs: options.maxWaitMs,\n\t\t},\n\t).then((box) => box.v);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAgB,0BAAmD;AAClE,QAAO;EAAE,OAAO;EAAM,UAAU;EAAO,SAAS;EAAG,cAAc;EAAI,MAAM,gBAAgB;EAAE;;;;;;;AAQ9F,SAAgB,4BAA4B,OAAyC;AACpF,OAAM;AACN,OAAM,WAAW;AACjB,OAAM,QAAQ;AACd,OAAM,eAAe;AAMrB,OAAM,KAAK,iBAAiB;;;;;;;;;AAU7B,MAAM,sBAAsB;AA6B5B,SAAS,YAAe,SAAqB,IAAwB;AACpE,QAAO,IAAI,SAAY,SAAS,WAAW;EAC1C,MAAM,QAAQ,iBAAiB;AAC9B,0BAAO,IAAI,MAAM,4CAA4C,GAAG,IAAI,CAAC;KACnE,GAAG;AAIN,UAAQ,KAAK,SAAS,OAAO,CAAC,cAAc;AAC3C,gBAAa,MAAM;IAClB;GACD;;;;;;;AAQH,SAAgB,mBACf,OACA,OACA,UAAqC,EAAE,EAC1B;CAKb,MAAM,gBAAgB,MAAM;CAI5B,MAAM,iBACL,QAAQ,mBAAmB,UAC3B,OAAO,SAAS,QAAQ,eAAe,IACvC,QAAQ,iBAAiB,IACtB,QAAQ,iBACR;CAIJ,MAAM,aACL,mBAAmB,SAChB,QAAQ,aACR,KAAK,IAAI,QAAQ,cAAc,GAAG,iBAAiB,oBAAoB;AAE3E,QAAO,aACN,MAAM,YAEL,MAAM,YAAY,MAAM,iBAAiB,gBAEtC,EAAE,GAAG,MAAM,OAAY,GACvB,OACH,mBAAmB;EAKnB,MAAM,QAAQ,YAA6B;GAC1C,MAAM,QAAQ,MAAM,OAAO;AAC3B,OAAI,gBAAgB,EAAE;AACrB,UAAM,QAAQ;AACd,UAAM,WAAW;AACjB,UAAM,eAAe;;AAEtB,UAAO,EAAE,GAAG,OAAO;MAChB;AAIJ,UAAQ,SACP,KAAK,WACE,cACA,OACN,CACD;AACD,SAAO,mBAAmB,SAAY,OAAO,YAAY,MAAM,eAAe;IAE/E;EACC;EACA,QAAQ,QAAQ;EAChB,WAAW,QAAQ;EACnB,CACD,CAAC,MAAM,QAAQ,IAAI,EAAE"}
1
+ {"version":3,"file":"single-flight-cache-C2exrGAi.mjs","names":[],"sources":["../src/utils/single-flight-cache.ts"],"sourcesContent":["/**\n * Global-scope async value cache with single-flight and poison-immunity.\n *\n * Built for the \"compute once for the lifetime of the JS global scope, read\n * on every request\" caches (site settings, search-health verification, ...).\n * That global scope is the process on Node and the isolate on Cloudflare\n * Workers — this helper is platform-neutral; the hazard it defends against is\n * specific to workerd but the cache itself is not.\n *\n * These caches must coalesce concurrent cold reads into one query — but the\n * obvious way to do that, caching the in-flight *promise* on a global and\n * awaiting it from later requests, is unsafe on workerd: if the request that\n * created the promise is cancelled mid-await (client disconnect, context\n * teardown), its continuation never runs, so the promise neither resolves nor\n * rejects. Every later request that awaits that shared promise then hangs\n * until the isolate is evicted (observed as 524s at the 100s wall, near-zero\n * CPU). A `.catch`/`.finally` that clears the cache doesn't help — a cancelled\n * request settles neither way.\n *\n * This cache stores the resolved *value* (not a promise) and coalesces via\n * `initWithLock`: one request becomes the owner and runs `fetch`, everyone\n * else polls for the published value and never awaits the owner's promise.\n * A cancelled owner can therefore never strand a waiter — the worst case is\n * the lock looks held until `deadlineMs`, then the next caller reclaims. The\n * owner's `fetch` is also anchored (waitUntil) so a cancelled originator's\n * query still completes and populates the cache, and bounded by\n * `ownerTimeoutMs` so a genuinely stuck fetch reclaims instead of hanging.\n *\n * Invalidation bumps `version`; reads compare against the version captured at\n * call time and refetch on mismatch.\n */\n\nimport { createInitLock, type InitLock, initWithLock } from \"./init-lock.js\";\n\nexport interface SingleFlightCache<T> {\n\t/** Last resolved value, valid only when `hasValue` is true. */\n\tvalue: T | null;\n\t/**\n\t * Presence flag, separate from `value` so that falsy/`undefined`/`void`\n\t * results cache correctly (a plain null check can't distinguish \"cached\n\t * undefined\" from \"never fetched\").\n\t */\n\thasValue: boolean;\n\t/** Invalidation counter; bumped by `invalidateSingleFlightCache`. */\n\tversion: number;\n\t/** The `version` the cached value was fetched at. */\n\tvalueVersion: number;\n\t/** Reclaimable single-flight lock (see init-lock.ts). */\n\tlock: InitLock;\n}\n\nexport function createSingleFlightCache<T>(): SingleFlightCache<T> {\n\treturn { value: null, hasValue: false, version: 0, valueVersion: -1, lock: createInitLock() };\n}\n\n/**\n * Force the next `singleFlightCached` call to refetch. An in-flight owner\n * fetched at the old version will not publish into the new version, so its\n * result is ignored by subsequent reads.\n */\nexport function invalidateSingleFlightCache(cache: SingleFlightCache<unknown>): void {\n\tcache.version++;\n\tcache.hasValue = false;\n\tcache.value = null;\n\tcache.valueVersion = -1;\n\t// Free the single-flight lock so a reader at the new version starts the\n\t// refetch immediately instead of waiting out a stale owner's deadline. A\n\t// still-running old-version owner can neither publish into the new version\n\t// (version gate) nor clobber a new owner (claim gate), so releasing here\n\t// is safe; the worst case is one brief duplicate fetch.\n\tcache.lock.ownerStartedAt = null;\n}\n\n/**\n * Headroom between the owner's own timeout and the waiter reclaim deadline.\n * The reclaim deadline must sit *above* `ownerTimeoutMs` so a slow-but-live\n * owner times out (and releases the lock) before a waiter would reclaim it —\n * otherwise a fetch slower than the deadline is superseded before it can\n * publish, and steady traffic turns that into a self-sustaining stampede.\n */\nconst RECLAIM_HEADROOM_MS = 5_000;\n\nexport interface SingleFlightCachedOptions {\n\t/**\n\t * Hand the in-flight fetch to the host's lifetime extender (waitUntil via\n\t * `after()`), so a cancelled originating request still drives it to\n\t * completion and populates the cache.\n\t */\n\tanchor?: (promise: Promise<void>) => void;\n\t/** Reclaim the single-flight lock if the owner holds it past this. */\n\tdeadlineMs?: number;\n\t/** Waiter poll interval. */\n\tpollMs?: number;\n\t/** Waiter gives up and throws after this long rather than hanging. */\n\tmaxWaitMs?: number;\n\t/**\n\t * Bound the owner's own `fetch`: if it doesn't settle within this, the\n\t * owner rejects (and releases the lock) instead of waiting indefinitely.\n\t * The anchored copy keeps running, so a slow-but-live fetch can still\n\t * publish for a later caller. Omit to leave the owner unbounded.\n\t */\n\townerTimeoutMs?: number;\n}\n\n/** Boxed cache hit so a `void`/falsy value is still distinguishable from a miss. */\ninterface Box<T> {\n\tv: T;\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\treject(new Error(`singleFlightCached: owner fetch exceeded ${ms}ms`));\n\t\t}, ms);\n\t\t// Settle from the underlying promise (whichever wins the race with the\n\t\t// timer), and always clear the timer so a resolved fetch doesn't leave\n\t\t// a pending timeout holding the isolate alive.\n\t\tpromise.then(resolve, reject).finally(() => {\n\t\t\tclearTimeout(timer);\n\t\t});\n\t});\n}\n\n/**\n * Return the cached value for `cache`, computing it via `fetch` under a\n * single-flight lock on a miss. Concurrent callers coalesce onto one fetch;\n * a cancelled owner cannot poison later callers (see file header).\n */\nexport function singleFlightCached<T>(\n\tcache: SingleFlightCache<T>,\n\tfetch: () => Promise<T>,\n\toptions: SingleFlightCachedOptions = {},\n): Promise<T> {\n\t// Capture the version once: a value published at this version satisfies\n\t// this call; an invalidation that lands mid-fetch makes the published\n\t// value stale for *later* calls (which captured the newer version) but\n\t// still valid for this one.\n\tconst versionAtCall = cache.version;\n\n\t// Ignore a non-positive / non-finite owner timeout rather than letting it\n\t// degenerate into an instant-reject (setTimeout coerces NaN/0 to ~0ms).\n\tconst ownerTimeoutMs =\n\t\toptions.ownerTimeoutMs !== undefined &&\n\t\tNumber.isFinite(options.ownerTimeoutMs) &&\n\t\toptions.ownerTimeoutMs > 0\n\t\t\t? options.ownerTimeoutMs\n\t\t\t: undefined;\n\n\t// Keep the reclaim deadline above the owner timeout (see RECLAIM_HEADROOM_MS):\n\t// the owner's own timeout, not a waiter reclaim, is the primary release.\n\tconst deadlineMs =\n\t\townerTimeoutMs === undefined\n\t\t\t? options.deadlineMs\n\t\t\t: Math.max(options.deadlineMs ?? 0, ownerTimeoutMs + RECLAIM_HEADROOM_MS);\n\n\treturn initWithLock<Box<T>>(\n\t\tcache.lock,\n\t\t() =>\n\t\t\tcache.hasValue && cache.valueVersion === versionAtCall\n\t\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- hasValue gates that `value` holds a real T\n\t\t\t\t\t({ v: cache.value as T } satisfies Box<T>)\n\t\t\t\t: null,\n\t\t(isCurrentClaim) => {\n\t\t\t// The real work, anchored independently so a cancelled owner's\n\t\t\t// fetch still settles and publishes. Publication is gated on the\n\t\t\t// claim so a reclaimed slow owner can't clobber the reclaimer's\n\t\t\t// value (same contract as initWithLock's own callers).\n\t\t\tconst real = (async (): Promise<Box<T>> => {\n\t\t\t\tconst value = await fetch();\n\t\t\t\tif (isCurrentClaim()) {\n\t\t\t\t\tcache.value = value;\n\t\t\t\t\tcache.hasValue = true;\n\t\t\t\t\tcache.valueVersion = versionAtCall;\n\t\t\t\t}\n\t\t\t\treturn { v: value };\n\t\t\t})();\n\t\t\t// Anchor the real fetch (not the timeout race): this is what must\n\t\t\t// survive a cancelled owner and run to publication. initWithLock is\n\t\t\t// left to manage only the lock; we don't double-anchor.\n\t\t\toptions.anchor?.(\n\t\t\t\treal.then(\n\t\t\t\t\t() => undefined,\n\t\t\t\t\t() => undefined,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ownerTimeoutMs === undefined ? real : withTimeout(real, ownerTimeoutMs);\n\t\t},\n\t\t{\n\t\t\tdeadlineMs,\n\t\t\tpollMs: options.pollMs,\n\t\t\tmaxWaitMs: options.maxWaitMs,\n\t\t},\n\t).then((box) => box.v);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAgB,0BAAmD;AAClE,QAAO;EAAE,OAAO;EAAM,UAAU;EAAO,SAAS;EAAG,cAAc;EAAI,MAAM,gBAAgB;EAAE;;;;;;;AAQ9F,SAAgB,4BAA4B,OAAyC;AACpF,OAAM;AACN,OAAM,WAAW;AACjB,OAAM,QAAQ;AACd,OAAM,eAAe;AAMrB,OAAM,KAAK,iBAAiB;;;;;;;;;AAU7B,MAAM,sBAAsB;AA6B5B,SAAS,YAAe,SAAqB,IAAwB;AACpE,QAAO,IAAI,SAAY,SAAS,WAAW;EAC1C,MAAM,QAAQ,iBAAiB;AAC9B,0BAAO,IAAI,MAAM,4CAA4C,GAAG,IAAI,CAAC;KACnE,GAAG;AAIN,UAAQ,KAAK,SAAS,OAAO,CAAC,cAAc;AAC3C,gBAAa,MAAM;IAClB;GACD;;;;;;;AAQH,SAAgB,mBACf,OACA,OACA,UAAqC,EAAE,EAC1B;CAKb,MAAM,gBAAgB,MAAM;CAI5B,MAAM,iBACL,QAAQ,mBAAmB,UAC3B,OAAO,SAAS,QAAQ,eAAe,IACvC,QAAQ,iBAAiB,IACtB,QAAQ,iBACR;CAIJ,MAAM,aACL,mBAAmB,SAChB,QAAQ,aACR,KAAK,IAAI,QAAQ,cAAc,GAAG,iBAAiB,oBAAoB;AAE3E,QAAO,aACN,MAAM,YAEL,MAAM,YAAY,MAAM,iBAAiB,gBAEtC,EAAE,GAAG,MAAM,OAAY,GACvB,OACH,mBAAmB;EAKnB,MAAM,QAAQ,YAA6B;GAC1C,MAAM,QAAQ,MAAM,OAAO;AAC3B,OAAI,gBAAgB,EAAE;AACrB,UAAM,QAAQ;AACd,UAAM,WAAW;AACjB,UAAM,eAAe;;AAEtB,UAAO,EAAE,GAAG,OAAO;MAChB;AAIJ,UAAQ,SACP,KAAK,WACE,cACA,OACN,CACD;AACD,SAAO,mBAAmB,SAAY,OAAO,YAAY,MAAM,eAAe;IAE/E;EACC;EACA,QAAQ,QAAQ;EAChB,WAAW,QAAQ;EACnB,CACD,CAAC,MAAM,QAAQ,IAAI,EAAE"}
@@ -10,4 +10,4 @@ async function getSiteBaseUrl(db, request) {
10
10
 
11
11
  //#endregion
12
12
  export { getSiteBaseUrl as t };
13
- //# sourceMappingURL=site-url-CcLy5D5s.mjs.map
13
+ //# sourceMappingURL=site-url-NGJT6NTU.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"site-url-CcLy5D5s.mjs","names":[],"sources":["../src/api/site-url.ts"],"sourcesContent":["/**\n * Resolve the canonical site base URL for use in outbound links (emails, etc.).\n *\n * Uses the stored `emdash:site_url` (set during setup on the real domain)\n * so that Host header spoofing in later requests cannot redirect users to\n * attacker-controlled domains.\n *\n * Falls back to the request URL only if no stored value exists (pre-setup).\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport type { Database } from \"../database/types.js\";\n\nexport async function getSiteBaseUrl(db: Kysely<Database>, request: Request): Promise<string> {\n\tconst options = new OptionsRepository(db);\n\tconst storedUrl = await options.get<string>(\"emdash:site_url\");\n\tif (storedUrl) {\n\t\treturn `${storedUrl}/_emdash`;\n\t}\n\t// Fallback: derive from request (only reached before setup completes)\n\tconst url = new URL(request.url);\n\treturn `${url.protocol}//${url.host}/_emdash`;\n}\n"],"mappings":";;;AAeA,eAAsB,eAAe,IAAsB,SAAmC;CAE7F,MAAM,YAAY,MADF,IAAI,kBAAkB,GAAG,CACT,IAAY,kBAAkB;AAC9D,KAAI,UACH,QAAO,GAAG,UAAU;CAGrB,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;AAChC,QAAO,GAAG,IAAI,SAAS,IAAI,IAAI,KAAK"}
1
+ {"version":3,"file":"site-url-NGJT6NTU.mjs","names":[],"sources":["../src/api/site-url.ts"],"sourcesContent":["/**\n * Resolve the canonical site base URL for use in outbound links (emails, etc.).\n *\n * Uses the stored `emdash:site_url` (set during setup on the real domain)\n * so that Host header spoofing in later requests cannot redirect users to\n * attacker-controlled domains.\n *\n * Falls back to the request URL only if no stored value exists (pre-setup).\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport type { Database } from \"../database/types.js\";\n\nexport async function getSiteBaseUrl(db: Kysely<Database>, request: Request): Promise<string> {\n\tconst options = new OptionsRepository(db);\n\tconst storedUrl = await options.get<string>(\"emdash:site_url\");\n\tif (storedUrl) {\n\t\treturn `${storedUrl}/_emdash`;\n\t}\n\t// Fallback: derive from request (only reached before setup completes)\n\tconst url = new URL(request.url);\n\treturn `${url.protocol}//${url.host}/_emdash`;\n}\n"],"mappings":";;;AAeA,eAAsB,eAAe,IAAsB,SAAmC;CAE7F,MAAM,YAAY,MADF,IAAI,kBAAkB,GAAG,CACT,IAAY,kBAAkB;AAC9D,KAAI,UACH,QAAO,GAAG,UAAU;CAGrB,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;AAChC,QAAO,GAAG,IAAI,SAAS,IAAI,IAAI,KAAK"}
@@ -0,0 +1,216 @@
1
+ import { sql } from "kysely";
2
+
3
+ //#region src/api/handlers/snapshot.ts
4
+ /**
5
+ * Verify HMAC-SHA256 preview signature using crypto.subtle.
6
+ * Returns true if the signature is valid and not expired.
7
+ */
8
+ async function verifyPreviewSignature(source, exp, sig, secret) {
9
+ if (exp < Date.now() / 1e3) return false;
10
+ const encoder = new TextEncoder();
11
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), {
12
+ name: "HMAC",
13
+ hash: "SHA-256"
14
+ }, false, ["verify"]);
15
+ const sigBytes = new Uint8Array(sig.length / 2);
16
+ for (let i = 0; i < sig.length; i += 2) sigBytes[i / 2] = parseInt(sig.substring(i, i + 2), 16);
17
+ return crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(`${source}:${exp}`));
18
+ }
19
+ /**
20
+ * Parse an X-Preview-Signature header value into its components.
21
+ *
22
+ * Format: "source:exp:sig" where source is a URL (contains colons),
23
+ * exp is a unix timestamp, and sig is 64 hex chars.
24
+ *
25
+ * Parses from the right since source URLs contain colons.
26
+ *
27
+ * @returns Parsed components, or null if the format is invalid
28
+ */
29
+ function parsePreviewSignatureHeader(header) {
30
+ const lastColon = header.lastIndexOf(":");
31
+ if (lastColon <= 0) return null;
32
+ const sig = header.substring(lastColon + 1);
33
+ if (sig.length !== 64) return null;
34
+ const rest = header.substring(0, lastColon);
35
+ const secondLastColon = rest.lastIndexOf(":");
36
+ if (secondLastColon <= 0) return null;
37
+ const source = rest.substring(0, secondLastColon);
38
+ const exp = parseInt(rest.substring(secondLastColon + 1), 10);
39
+ if (isNaN(exp) || source.length === 0) return null;
40
+ return {
41
+ source,
42
+ exp,
43
+ sig
44
+ };
45
+ }
46
+ const MEDIA_FILE_PREFIX = "/_emdash/api/media/file/";
47
+ /**
48
+ * Parse a JSON string value and inject `src` for local media objects.
49
+ * Returns the original string if it's not a local media value.
50
+ */
51
+ function injectMediaSrc(jsonStr, origin) {
52
+ try {
53
+ const obj = JSON.parse(jsonStr);
54
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return jsonStr;
55
+ if (injectMediaSrcInto(obj, origin)) return JSON.stringify(obj);
56
+ return jsonStr;
57
+ } catch {
58
+ return jsonStr;
59
+ }
60
+ }
61
+ function isRecord(value) {
62
+ return typeof value === "object" && value !== null && !Array.isArray(value);
63
+ }
64
+ /**
65
+ * Recursively walk an object and inject `src` into local media values.
66
+ * Returns true if any modifications were made.
67
+ */
68
+ function injectMediaSrcInto(obj, origin) {
69
+ let modified = false;
70
+ if ((obj.provider === "local" || !obj.provider && obj.id && obj.meta) && !obj.src) {
71
+ const storageKey = (isRecord(obj.meta) ? obj.meta : void 0)?.storageKey ?? obj.id;
72
+ if (typeof storageKey === "string" && storageKey) {
73
+ obj.src = `${origin}${MEDIA_FILE_PREFIX}${storageKey}`;
74
+ modified = true;
75
+ }
76
+ }
77
+ for (const value of Object.values(obj)) if (Array.isArray(value)) {
78
+ for (const item of value) if (isRecord(item)) {
79
+ if (injectMediaSrcInto(item, origin)) modified = true;
80
+ }
81
+ } else if (isRecord(value)) {
82
+ if (injectMediaSrcInto(value, origin)) modified = true;
83
+ }
84
+ return modified;
85
+ }
86
+ /**
87
+ * Safe identifier pattern for snapshot table names.
88
+ * More permissive than validateIdentifier() — allows leading underscores
89
+ * (needed for system tables like _emdash_collections).
90
+ */
91
+ const SAFE_TABLE_NAME = /^[a-z_][a-z0-9_]*$/;
92
+ /**
93
+ * System tables included in snapshots.
94
+ * Content tables (ec_*) are discovered dynamically.
95
+ */
96
+ const SYSTEM_TABLES = [
97
+ "_emdash_collections",
98
+ "_emdash_fields",
99
+ "_emdash_taxonomy_defs",
100
+ "_emdash_menus",
101
+ "_emdash_menu_items",
102
+ "_emdash_sections",
103
+ "_emdash_widget_areas",
104
+ "_emdash_widgets",
105
+ "_emdash_seo",
106
+ "_emdash_migrations",
107
+ "taxonomies",
108
+ "content_taxonomies",
109
+ "media",
110
+ "options",
111
+ "revisions"
112
+ ];
113
+ /**
114
+ * Table name prefixes excluded from snapshots (auth/security data).
115
+ */
116
+ const EXCLUDED_PREFIXES = [
117
+ "_emdash_api_tokens",
118
+ "_emdash_oauth_tokens",
119
+ "_emdash_authorization_codes",
120
+ "_emdash_device_codes",
121
+ "_emdash_migrations_lock",
122
+ "_plugin_",
123
+ "users",
124
+ "sessions",
125
+ "credentials",
126
+ "challenges"
127
+ ];
128
+ /**
129
+ * Options key prefixes safe for inclusion in snapshots.
130
+ *
131
+ * The options table contains plugin secrets (plugin:*), passkey challenges
132
+ * (emdash:passkey_pending:*), and setup state that must not leak to
133
+ * preview databases. Only site-level rendering settings are needed.
134
+ */
135
+ const SAFE_OPTIONS_PREFIXES = ["site:"];
136
+ function isExcluded(tableName) {
137
+ return EXCLUDED_PREFIXES.some((prefix) => tableName.startsWith(prefix));
138
+ }
139
+ /**
140
+ * Generate a portable database snapshot.
141
+ *
142
+ * Discovers ec_* content tables dynamically, exports system tables
143
+ * needed for rendering, and includes schema info for table recreation.
144
+ */
145
+ async function generateSnapshot(db, options) {
146
+ const includeDrafts = options?.includeDrafts ?? false;
147
+ const includeTrashed = options?.includeTrashed ?? false;
148
+ const optionPrefixes = options?.optionPrefixes ?? SAFE_OPTIONS_PREFIXES;
149
+ const allTables = [...(await sql`
150
+ SELECT name FROM sqlite_master
151
+ WHERE type = 'table'
152
+ AND name LIKE 'ec_%'
153
+ ORDER BY name
154
+ `.execute(db)).rows.map((r) => r.name), ...SYSTEM_TABLES];
155
+ const tables = {};
156
+ const schema = {};
157
+ for (const tableName of allTables) {
158
+ if (isExcluded(tableName)) continue;
159
+ if (!SAFE_TABLE_NAME.test(tableName)) continue;
160
+ try {
161
+ const pragmaResult = await sql`
162
+ PRAGMA table_info(${sql.raw(`"${tableName}"`)})
163
+ `.execute(db);
164
+ if (pragmaResult.rows.length === 0) continue;
165
+ const columns = pragmaResult.rows.map((r) => r.name);
166
+ const types = {};
167
+ for (const row of pragmaResult.rows) types[row.name] = row.type || "TEXT";
168
+ schema[tableName] = {
169
+ columns,
170
+ types
171
+ };
172
+ let rows;
173
+ if (tableName.startsWith("ec_")) if (includeTrashed) rows = (await sql`
174
+ SELECT * FROM ${sql.raw(`"${tableName}"`)}
175
+ `.execute(db)).rows;
176
+ else if (includeDrafts) rows = (await sql`
177
+ SELECT * FROM ${sql.raw(`"${tableName}"`)}
178
+ WHERE deleted_at IS NULL
179
+ `.execute(db)).rows;
180
+ else rows = (await sql`
181
+ SELECT * FROM ${sql.raw(`"${tableName}"`)}
182
+ WHERE deleted_at IS NULL
183
+ AND (status = 'published' OR (status = 'scheduled' AND scheduled_at <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')))
184
+ `.execute(db)).rows;
185
+ else if (tableName === "options") rows = (await sql`
186
+ SELECT * FROM ${sql.raw(`"${tableName}"`)}
187
+ `.execute(db)).rows.filter((row) => {
188
+ const name = typeof row.name === "string" ? row.name : "";
189
+ return optionPrefixes.some((prefix) => name.startsWith(prefix));
190
+ });
191
+ else rows = (await sql`
192
+ SELECT * FROM ${sql.raw(`"${tableName}"`)}
193
+ `.execute(db)).rows;
194
+ if (rows.length > 0) tables[tableName] = rows;
195
+ } catch {}
196
+ }
197
+ if (options?.origin) {
198
+ const origin = options.origin;
199
+ for (const [tableName, rows] of Object.entries(tables)) {
200
+ if (!tableName.startsWith("ec_")) continue;
201
+ for (const row of rows) for (const [col, value] of Object.entries(row)) {
202
+ if (typeof value !== "string" || !value.startsWith("{")) continue;
203
+ row[col] = injectMediaSrc(value, origin);
204
+ }
205
+ }
206
+ }
207
+ return {
208
+ tables,
209
+ schema,
210
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
211
+ };
212
+ }
213
+
214
+ //#endregion
215
+ export { parsePreviewSignatureHeader as n, verifyPreviewSignature as r, generateSnapshot as t };
216
+ //# sourceMappingURL=snapshot-BdpUJKD-.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-BdpUJKD-.mjs","names":[],"sources":["../src/api/handlers/snapshot.ts"],"sourcesContent":["/**\n * Snapshot handler — generates a portable database snapshot.\n *\n * Returns all content tables, schema definitions, and supporting data\n * needed to render content in an isolated preview database.\n *\n * Used by:\n * - DO preview database (EmDashPreviewDB.populateFromSnapshot)\n * - Future: CLI export, backup, site migration\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\n\n// ─�� Preview signature verification ──────────────────────────────\n\n/**\n * Verify HMAC-SHA256 preview signature using crypto.subtle.\n * Returns true if the signature is valid and not expired.\n */\nexport async function verifyPreviewSignature(\n\tsource: string,\n\texp: number,\n\tsig: string,\n\tsecret: string,\n): Promise<boolean> {\n\tif (exp < Date.now() / 1000) return false;\n\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\n\tconst sigBytes = new Uint8Array(sig.length / 2);\n\tfor (let i = 0; i < sig.length; i += 2) {\n\t\tsigBytes[i / 2] = parseInt(sig.substring(i, i + 2), 16);\n\t}\n\n\treturn crypto.subtle.verify(\"HMAC\", key, sigBytes, encoder.encode(`${source}:${exp}`));\n}\n\n/**\n * Parse an X-Preview-Signature header value into its components.\n *\n * Format: \"source:exp:sig\" where source is a URL (contains colons),\n * exp is a unix timestamp, and sig is 64 hex chars.\n *\n * Parses from the right since source URLs contain colons.\n *\n * @returns Parsed components, or null if the format is invalid\n */\nexport function parsePreviewSignatureHeader(\n\theader: string,\n): { source: string; exp: number; sig: string } | null {\n\tconst lastColon = header.lastIndexOf(\":\");\n\tif (lastColon <= 0) return null;\n\n\tconst sig = header.substring(lastColon + 1);\n\tif (sig.length !== 64) return null;\n\n\tconst rest = header.substring(0, lastColon);\n\tconst secondLastColon = rest.lastIndexOf(\":\");\n\tif (secondLastColon <= 0) return null;\n\n\tconst source = rest.substring(0, secondLastColon);\n\tconst exp = parseInt(rest.substring(secondLastColon + 1), 10);\n\n\tif (isNaN(exp) || source.length === 0) return null;\n\n\treturn { source, exp, sig };\n}\n\n// ── Media URL rewriting ─────────────────────────────────────────\n\nconst MEDIA_FILE_PREFIX = \"/_emdash/api/media/file/\";\n\n/**\n * Parse a JSON string value and inject `src` for local media objects.\n * Returns the original string if it's not a local media value.\n */\nfunction injectMediaSrc(jsonStr: string, origin: string): string {\n\ttry {\n\t\tconst obj = JSON.parse(jsonStr);\n\t\tif (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) return jsonStr;\n\t\tif (injectMediaSrcInto(obj, origin)) {\n\t\t\treturn JSON.stringify(obj);\n\t\t}\n\t\treturn jsonStr;\n\t} catch {\n\t\treturn jsonStr;\n\t}\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Recursively walk an object and inject `src` into local media values.\n * Returns true if any modifications were made.\n */\nfunction injectMediaSrcInto(obj: Record<string, unknown>, origin: string): boolean {\n\tlet modified = false;\n\n\t// Check if this object itself is a local media value\n\tif ((obj.provider === \"local\" || (!obj.provider && obj.id && obj.meta)) && !obj.src) {\n\t\tconst meta = isRecord(obj.meta) ? obj.meta : undefined;\n\t\tconst storageKey = meta?.storageKey ?? obj.id;\n\t\tif (typeof storageKey === \"string\" && storageKey) {\n\t\t\tobj.src = `${origin}${MEDIA_FILE_PREFIX}${storageKey}`;\n\t\t\tmodified = true;\n\t\t}\n\t}\n\n\t// Recurse into nested objects/arrays (e.g. Portable Text with image blocks)\n\tfor (const value of Object.values(obj)) {\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tif (isRecord(item)) {\n\t\t\t\t\tif (injectMediaSrcInto(item, origin)) {\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isRecord(value)) {\n\t\t\tif (injectMediaSrcInto(value, origin)) {\n\t\t\t\tmodified = true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn modified;\n}\n\n// ── Snapshot generation ─────────────────────────────────────────\n\n/**\n * Safe identifier pattern for snapshot table names.\n * More permissive than validateIdentifier() — allows leading underscores\n * (needed for system tables like _emdash_collections).\n */\nconst SAFE_TABLE_NAME = /^[a-z_][a-z0-9_]*$/;\n\n/** Snapshot shape consumed by the DO preview database */\nexport interface Snapshot {\n\ttables: Record<string, Record<string, unknown>[]>;\n\tschema: Record<\n\t\tstring,\n\t\t{\n\t\t\tcolumns: string[];\n\t\t\ttypes?: Record<string, string>;\n\t\t}\n\t>;\n\tgeneratedAt: string;\n}\n\n/**\n * System tables included in snapshots.\n * Content tables (ec_*) are discovered dynamically.\n */\nconst SYSTEM_TABLES = [\n\t\"_emdash_collections\",\n\t\"_emdash_fields\",\n\t\"_emdash_taxonomy_defs\",\n\t\"_emdash_menus\",\n\t\"_emdash_menu_items\",\n\t\"_emdash_sections\",\n\t\"_emdash_widget_areas\",\n\t\"_emdash_widgets\",\n\t\"_emdash_seo\",\n\t\"_emdash_migrations\",\n\t\"taxonomies\",\n\t\"content_taxonomies\",\n\t\"media\",\n\t\"options\",\n\t\"revisions\",\n];\n\n/**\n * Table name prefixes excluded from snapshots (auth/security data).\n */\nconst EXCLUDED_PREFIXES = [\n\t\"_emdash_api_tokens\",\n\t\"_emdash_oauth_tokens\",\n\t\"_emdash_authorization_codes\",\n\t\"_emdash_device_codes\",\n\t\"_emdash_migrations_lock\",\n\t\"_plugin_\",\n\t\"users\",\n\t\"sessions\",\n\t\"credentials\",\n\t\"challenges\",\n];\n\n/**\n * Options key prefixes safe for inclusion in snapshots.\n *\n * The options table contains plugin secrets (plugin:*), passkey challenges\n * (emdash:passkey_pending:*), and setup state that must not leak to\n * preview databases. Only site-level rendering settings are needed.\n */\nconst SAFE_OPTIONS_PREFIXES = [\"site:\"];\n\nfunction isExcluded(tableName: string): boolean {\n\treturn EXCLUDED_PREFIXES.some((prefix) => tableName.startsWith(prefix));\n}\n\n/** Column info from PRAGMA table_info */\ninterface ColumnInfo {\n\tname: string;\n\ttype: string;\n}\n\nexport interface GenerateSnapshotOptions {\n\t/** Include draft and scheduled content (default: false) */\n\tincludeDrafts?: boolean;\n\t/** Include trashed content (deleted_at set). Used by backups (default: false) */\n\tincludeTrashed?: boolean;\n\t/** Origin URL for absolutizing local media URLs (e.g. \"https://mysite.com\") */\n\torigin?: string;\n\t/**\n\t * Allowlist of options-table key prefixes to include (default:\n\t * `SAFE_OPTIONS_PREFIXES`). Callers widening this must never include a\n\t * prefix that matches secrets (`emdash:preview_secret`, `plugin:`,\n\t * `emdash:passkey_pending:`) — the output may be user-downloadable.\n\t */\n\toptionPrefixes?: string[];\n}\n\n/**\n * Generate a portable database snapshot.\n *\n * Discovers ec_* content tables dynamically, exports system tables\n * needed for rendering, and includes schema info for table recreation.\n */\nexport async function generateSnapshot(\n\tdb: Kysely<Database>,\n\toptions?: GenerateSnapshotOptions,\n): Promise<Snapshot> {\n\tconst includeDrafts = options?.includeDrafts ?? false;\n\tconst includeTrashed = options?.includeTrashed ?? false;\n\tconst optionPrefixes = options?.optionPrefixes ?? SAFE_OPTIONS_PREFIXES;\n\n\t// Discover all ec_* content tables\n\tconst tableResult = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'table'\n\t\tAND name LIKE 'ec_%'\n\t\tORDER BY name\n\t`.execute(db);\n\n\tconst contentTables = tableResult.rows.map((r) => r.name);\n\n\t// Build list of all tables to export\n\tconst allTables = [...contentTables, ...SYSTEM_TABLES];\n\n\tconst tables: Record<string, Record<string, unknown>[]> = {};\n\tconst schema: Record<string, { columns: string[]; types?: Record<string, string> }> = {};\n\n\tfor (const tableName of allTables) {\n\t\tif (isExcluded(tableName)) continue;\n\n\t\t// Validate identifier before interpolating into sql.raw().\n\t\t// SYSTEM_TABLES are hardcoded and safe, but ec_* names come from\n\t\t// sqlite_master and must be validated.\n\t\tif (!SAFE_TABLE_NAME.test(tableName)) continue;\n\n\t\ttry {\n\t\t\t// Get column info via PRAGMA\n\t\t\tconst pragmaResult = await sql<ColumnInfo>`\n\t\t\t\tPRAGMA table_info(${sql.raw(`\"${tableName}\"`)})\n\t\t\t`.execute(db);\n\n\t\t\tif (pragmaResult.rows.length === 0) continue;\n\n\t\t\tconst columns = pragmaResult.rows.map((r) => r.name);\n\t\t\tconst types: Record<string, string> = {};\n\t\t\tfor (const row of pragmaResult.rows) {\n\t\t\t\ttypes[row.name] = row.type || \"TEXT\";\n\t\t\t}\n\n\t\t\tschema[tableName] = { columns, types };\n\n\t\t\t// Fetch rows\n\t\t\tlet rows: Record<string, unknown>[];\n\n\t\t\tif (tableName.startsWith(\"ec_\")) {\n\t\t\t\tif (includeTrashed) {\n\t\t\t\t\t// Everything, including trash — full-fidelity backup export\n\t\t\t\t\trows = (\n\t\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t\t`.execute(db)\n\t\t\t\t\t).rows;\n\t\t\t\t} else if (includeDrafts) {\n\t\t\t\t\t// Include all non-deleted content (published, draft, scheduled)\n\t\t\t\t\trows = (\n\t\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t`.execute(db)\n\t\t\t\t\t).rows;\n\t\t\t\t} else {\n\t\t\t\t\t// Only export published content\n\t\t\t\t\trows = (\n\t\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t\tAND (status = 'published' OR (status = 'scheduled' AND scheduled_at <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')))\n\t\t\t\t\t`.execute(db)\n\t\t\t\t\t).rows;\n\t\t\t\t}\n\t\t\t} else if (tableName === \"options\") {\n\t\t\t\t// Filter options to safe rendering-only prefixes.\n\t\t\t\t// Excludes plugin secrets, passkey challenges, and setup state.\n\t\t\t\trows = (\n\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t`.execute(db)\n\t\t\t\t).rows.filter((row) => {\n\t\t\t\t\tconst name = typeof row.name === \"string\" ? row.name : \"\";\n\t\t\t\t\treturn optionPrefixes.some((prefix) => name.startsWith(prefix));\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\trows = (\n\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t`.execute(db)\n\t\t\t\t).rows;\n\t\t\t}\n\n\t\t\tif (rows.length > 0) {\n\t\t\t\ttables[tableName] = rows;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Table might not exist yet (e.g. pre-migration) — skip silently\n\t\t}\n\t}\n\n\t// Absolutize local media URLs in content tables so snapshots are portable.\n\t// Local image fields are stored as JSON with provider:\"local\" and\n\t// meta.storageKey but no src — the URL is derived at render time.\n\t// For snapshots consumed by external preview services, inject src now.\n\tif (options?.origin) {\n\t\tconst origin = options.origin;\n\t\tfor (const [tableName, rows] of Object.entries(tables)) {\n\t\t\tif (!tableName.startsWith(\"ec_\")) continue;\n\t\t\tfor (const row of rows) {\n\t\t\t\tfor (const [col, value] of Object.entries(row)) {\n\t\t\t\t\tif (typeof value !== \"string\" || !value.startsWith(\"{\")) continue;\n\t\t\t\t\trow[col] = injectMediaSrc(value, origin);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ttables,\n\t\tschema,\n\t\tgeneratedAt: new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
@@ -329,4 +329,4 @@ function stripCredentialHeaders(init) {
329
329
 
330
330
  //#endregion
331
331
  export { validateExternalUrl as a, stripCredentialHeaders as i, resolveAndValidateExternalUrl as n, ssrfSafeFetch as r, SsrfError as t };
332
- //# sourceMappingURL=ssrf-TrKYJAy0.mjs.map
332
+ //# sourceMappingURL=ssrf-CviKqWmq.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssrf-TrKYJAy0.mjs","names":[],"sources":["../src/security/ssrf.ts"],"sourcesContent":["/**\n * SSRF protection for import URLs.\n *\n * Validates that URLs don't target internal/private network addresses.\n * Applied before any fetch() call in the import pipeline.\n */\n\nconst IPV4_MAPPED_IPV6_DOTTED_PATTERN = /^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/i;\nconst IPV4_MAPPED_IPV6_HEX_PATTERN = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\nconst IPV4_TRANSLATED_HEX_PATTERN = /^::ffff:0:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\nconst IPV6_EXPANDED_MAPPED_PATTERN =\n\t/^0{0,4}:0{0,4}:0{0,4}:0{0,4}:0{0,4}:ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\n\n/**\n * IPv4-compatible (deprecated) addresses: ::XXXX:XXXX\n *\n * The WHATWG URL parser normalizes [::127.0.0.1] to [::7f00:1] (no ffff prefix).\n * These are deprecated but still parsed, and bypass the ffff-based checks.\n */\nconst IPV4_COMPATIBLE_HEX_PATTERN = /^::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\n\n/**\n * NAT64 prefix (RFC 6052): 64:ff9b::XXXX:XXXX\n *\n * Used by NAT64 gateways to embed IPv4 addresses in IPv6.\n * [64:ff9b::127.0.0.1] normalizes to [64:ff9b::7f00:1].\n */\nconst NAT64_HEX_PATTERN = /^64:ff9b::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\n\nconst IPV6_BRACKET_PATTERN = /^\\[|\\]$/g;\n\n/** Match fc00::/7 ULA — first byte 0xfc or 0xfd followed by any byte. */\nconst IPV6_ULA_FC_PATTERN = /^fc[0-9a-f]{2}:/;\nconst IPV6_ULA_FD_PATTERN = /^fd[0-9a-f]{2}:/;\n\n/** Strip trailing dots from an FQDN-form hostname (\"localhost.\" -> \"localhost\"). */\nconst TRAILING_DOT_PATTERN = /\\.+$/;\n\n/**\n * Private and reserved IP ranges that should never be fetched.\n *\n * Includes:\n * - Loopback (127.0.0.0/8)\n * - Private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)\n * - Link-local (169.254.0.0/16)\n * - Cloud metadata (169.254.169.254 — AWS/GCP/Azure)\n * - IPv6 loopback and link-local\n */\nconst BLOCKED_PATTERNS: Array<{ start: number; end: number }> = [\n\t// 127.0.0.0/8 — loopback\n\t{ start: ip4ToNum(127, 0, 0, 0), end: ip4ToNum(127, 255, 255, 255) },\n\t// 10.0.0.0/8 — private\n\t{ start: ip4ToNum(10, 0, 0, 0), end: ip4ToNum(10, 255, 255, 255) },\n\t// 172.16.0.0/12 — private\n\t{ start: ip4ToNum(172, 16, 0, 0), end: ip4ToNum(172, 31, 255, 255) },\n\t// 192.168.0.0/16 — private\n\t{ start: ip4ToNum(192, 168, 0, 0), end: ip4ToNum(192, 168, 255, 255) },\n\t// 169.254.0.0/16 — link-local (includes cloud metadata endpoint)\n\t{ start: ip4ToNum(169, 254, 0, 0), end: ip4ToNum(169, 254, 255, 255) },\n\t// 0.0.0.0/8 — current network\n\t{ start: ip4ToNum(0, 0, 0, 0), end: ip4ToNum(0, 255, 255, 255) },\n];\n\n// Bracket-stripped form is used for lookups (validateExternalUrl strips\n// brackets from parsed.hostname before checking), so \"::1\" appears here\n// without brackets. The \"::1\" case is already covered by isPrivateIp, but\n// keeping it here makes the intent explicit and gives a clearer error\n// message for the common `http://[::1]/` form.\nconst BLOCKED_HOSTNAMES = new Set([\n\t\"localhost\",\n\t\"metadata.google.internal\",\n\t\"metadata.google\",\n\t\"::1\",\n]);\n\n/**\n * Wildcard DNS services that publicly resolve arbitrary IPs embedded in the\n * hostname. Commonly used in local dev and by SSRF exploit tooling to bypass\n * hostname-only blocklists (e.g. 127.0.0.1.nip.io -> 127.0.0.1).\n *\n * Matched case-insensitively as a suffix, so both the apex and any subdomain\n * are blocked.\n */\nconst BLOCKED_HOSTNAME_SUFFIXES = [\n\t\"nip.io\",\n\t\"sslip.io\",\n\t\"xip.io\",\n\t\"traefik.me\",\n\t\"lvh.me\",\n\t\"localtest.me\",\n];\n\n/** Blocked URL schemes */\nconst ALLOWED_SCHEMES = new Set([\"http:\", \"https:\"]);\n\nfunction ip4ToNum(a: number, b: number, c: number, d: number): number {\n\treturn ((a << 24) | (b << 16) | (c << 8) | d) >>> 0;\n}\n\nfunction parseIpv4(ip: string): number | null {\n\tconst parts = ip.split(\".\");\n\tif (parts.length !== 4) return null;\n\n\tconst nums = parts.map(Number);\n\tif (nums.some((n) => isNaN(n) || n < 0 || n > 255)) return null;\n\n\treturn ip4ToNum(nums[0], nums[1], nums[2], nums[3]);\n}\n\n/**\n * Convert IPv4-mapped/translated IPv6 addresses from hex form back to IPv4.\n *\n * The WHATWG URL parser normalizes dotted-decimal to hex:\n * [::ffff:127.0.0.1] -> [::ffff:7f00:1]\n * [::ffff:169.254.169.254] -> [::ffff:a9fe:a9fe]\n *\n * Without this conversion, the hex forms bypass isPrivateIp() regex checks.\n */\nexport function normalizeIPv6MappedToIPv4(ip: string): string | null {\n\t// Match hex-form IPv4-mapped IPv6: ::ffff:XXXX:XXXX\n\tlet match = ip.match(IPV4_MAPPED_IPV6_HEX_PATTERN);\n\tif (!match) {\n\t\t// Match IPv4-translated (RFC 6052): ::ffff:0:XXXX:XXXX\n\t\tmatch = ip.match(IPV4_TRANSLATED_HEX_PATTERN);\n\t}\n\tif (!match) {\n\t\t// Match fully expanded form: 0000:0000:0000:0000:0000:ffff:XXXX:XXXX\n\t\tmatch = ip.match(IPV6_EXPANDED_MAPPED_PATTERN);\n\t}\n\tif (!match) {\n\t\t// Match IPv4-compatible (deprecated) form: ::XXXX:XXXX (no ffff prefix)\n\t\tmatch = ip.match(IPV4_COMPATIBLE_HEX_PATTERN);\n\t}\n\tif (!match) {\n\t\t// Match NAT64 prefix (RFC 6052): 64:ff9b::XXXX:XXXX\n\t\tmatch = ip.match(NAT64_HEX_PATTERN);\n\t}\n\tif (match) {\n\t\tconst high = parseInt(match[1] ?? \"\", 16);\n\t\tconst low = parseInt(match[2] ?? \"\", 16);\n\t\treturn `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;\n\t}\n\treturn null;\n}\n\nfunction isPrivateIp(ip: string): boolean {\n\t// Normalize IPv6 strings to lowercase. `new URL().hostname` already\n\t// lowercases, but resolver output (from DoH or an injected resolver) may\n\t// not. Without this, \"FE80::1\" bypasses the link-local check.\n\tconst normalized = ip.toLowerCase();\n\n\t// Handle IPv6 loopback\n\tif (normalized === \"::1\" || normalized === \"::ffff:127.0.0.1\") return true;\n\n\t// Handle IPv4-mapped IPv6 in hex form (WHATWG URL parser normalizes to this)\n\t// e.g. ::ffff:7f00:1 -> 127.0.0.1, ::ffff:a9fe:a9fe -> 169.254.169.254\n\tconst hexIpv4 = normalizeIPv6MappedToIPv4(normalized);\n\tif (hexIpv4) return isPrivateIp(hexIpv4);\n\n\t// Handle IPv4-mapped IPv6 in dotted-decimal form\n\tconst v4Match = normalized.match(IPV4_MAPPED_IPV6_DOTTED_PATTERN);\n\tconst ipv4 = v4Match ? v4Match[1] : normalized;\n\n\tconst num = parseIpv4(ipv4);\n\tif (num === null) {\n\t\t// If we can't parse it, block IPv6 addresses that look internal.\n\t\t// fc00::/7 is Unique Local (first byte 0xfc or 0xfd), fe80::/10 is\n\t\t// link-local. Only match when followed by hex digit + colon to avoid\n\t\t// collisions with hypothetical non-address strings.\n\t\treturn (\n\t\t\tnormalized.startsWith(\"fe80:\") ||\n\t\t\tIPV6_ULA_FC_PATTERN.test(normalized) ||\n\t\t\tIPV6_ULA_FD_PATTERN.test(normalized)\n\t\t);\n\t}\n\n\treturn BLOCKED_PATTERNS.some((range) => num >= range.start && num <= range.end);\n}\n\n/**\n * Error thrown when SSRF protection blocks a URL.\n */\nexport class SsrfError extends Error {\n\tcode = \"SSRF_BLOCKED\" as const;\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"SsrfError\";\n\t}\n}\n\n/**\n * Validate that a URL is safe to fetch (not targeting internal networks).\n *\n * Checks:\n * 1. URL is well-formed with http/https scheme\n * 2. Hostname is not a known internal name (localhost, metadata endpoints)\n * 3. If hostname is an IP literal, it's not in a private range\n *\n * Note: DNS rebinding attacks are not fully mitigated (hostname could resolve\n * to a private IP). Full protection requires resolving DNS and checking the IP\n * before connecting, which needs a custom fetch implementation. This covers\n * the most common SSRF vectors.\n *\n * @throws SsrfError if the URL targets an internal address\n */\n/** Maximum number of redirects to follow in ssrfSafeFetch */\nconst MAX_REDIRECTS = 5;\n\nexport function validateExternalUrl(url: string): URL {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\tthrow new SsrfError(\"Invalid URL\");\n\t}\n\n\t// Only allow http/https\n\tif (!ALLOWED_SCHEMES.has(parsed.protocol)) {\n\t\tthrow new SsrfError(`Scheme '${parsed.protocol}' is not allowed`);\n\t}\n\n\t// Strip brackets from IPv6 hostname\n\tconst hostname = parsed.hostname.replace(IPV6_BRACKET_PATTERN, \"\");\n\n\t// Normalize the hostname for blocklist matching: lowercase + strip any\n\t// trailing dots. WHATWG preserves trailing dots on .hostname, so without\n\t// this normalization \"localhost.\" and \"nip.io.\" bypass the checks.\n\tconst normalizedHost = hostname.toLowerCase().replace(TRAILING_DOT_PATTERN, \"\");\n\n\t// Check against known internal hostnames\n\tif (BLOCKED_HOSTNAMES.has(normalizedHost)) {\n\t\tthrow new SsrfError(\"URLs targeting internal hosts are not allowed\");\n\t}\n\n\t// Check against wildcard DNS services used by SSRF tooling to bypass\n\t// hostname-only checks. Match the apex and any subdomain.\n\tfor (const suffix of BLOCKED_HOSTNAME_SUFFIXES) {\n\t\tif (normalizedHost === suffix || normalizedHost.endsWith(`.${suffix}`)) {\n\t\t\tthrow new SsrfError(\"URLs targeting wildcard DNS services are not allowed\");\n\t\t}\n\t}\n\n\t// Check if hostname is an IP address in a private range. Use the\n\t// normalized form so \"127.0.0.1..\" and friends don't bypass parseIpv4\n\t// (which rejects extra trailing dots).\n\tif (isPrivateIp(normalizedHost)) {\n\t\tthrow new SsrfError(\"URLs targeting private IP addresses are not allowed\");\n\t}\n\n\treturn parsed;\n}\n\n// ---------------------------------------------------------------------------\n// DNS-aware validation\n// ---------------------------------------------------------------------------\n\n/**\n * A resolver that maps a hostname to a list of IPv4/IPv6 addresses.\n * Injectable so callers can swap in OS-level DNS on Node, stub it in tests,\n * or point to a different DoH endpoint.\n */\nexport type DnsResolver = (hostname: string) => Promise<string[]>;\n\n/**\n * Module-level default resolver. Tests can swap this with a stub so fetch\n * mocks don't see unexpected DoH round-trips. Production code should leave\n * it alone.\n */\nlet defaultResolver: DnsResolver | null = null;\n\n/** Override the default DNS resolver. Returns the previous value. */\nexport function setDefaultDnsResolver(resolver: DnsResolver | null): DnsResolver | null {\n\tconst previous = defaultResolver;\n\tdefaultResolver = resolver;\n\treturn previous;\n}\n\n/** Timeout for a single DoH request, in milliseconds. */\nconst DOH_TIMEOUT_MS = 3000;\n\n/** Default DoH endpoint — Cloudflare's public resolver. */\nconst DEFAULT_DOH_URL = \"https://cloudflare-dns.com/dns-query\";\n\ninterface DohAnswer {\n\tdata: string;\n}\n\ninterface DohResponse {\n\tStatus: number;\n\tAnswer: DohAnswer[];\n}\n\nfunction hasProperty<K extends string>(obj: unknown, key: K): obj is Record<K, unknown> {\n\treturn typeof obj === \"object\" && obj !== null && key in obj;\n}\n\n/**\n * Narrow an unknown JSON body to a DohResponse shape we can read safely.\n * Throws if the body doesn't look like a DoH response — a malformed body is\n * indistinguishable from a failure and must not be silently treated as empty.\n */\nfunction parseDohResponse(raw: unknown): DohResponse {\n\tif (!hasProperty(raw, \"Status\") || typeof raw.Status !== \"number\") {\n\t\tthrow new Error(\"DoH response missing Status field\");\n\t}\n\tconst answers: DohAnswer[] = [];\n\tif (hasProperty(raw, \"Answer\") && Array.isArray(raw.Answer)) {\n\t\tfor (const entry of raw.Answer) {\n\t\t\tif (hasProperty(entry, \"data\") && typeof entry.data === \"string\") {\n\t\t\t\tanswers.push({ data: entry.data });\n\t\t\t}\n\t\t}\n\t}\n\treturn { Status: raw.Status, Answer: answers };\n}\n\n/**\n * Resolve a hostname via DNS over HTTPS (Cloudflare). Returns all A and AAAA\n * records. Works in both Workers and Node without requiring node:dns.\n *\n * Fails closed: any network error, non-2xx response, or DNS rcode != 0\n * causes a rejected promise so the calling validator treats it as a block.\n */\nexport const cloudflareDohResolver: DnsResolver = async (hostname) => {\n\tasync function query(type: \"A\" | \"AAAA\"): Promise<string[]> {\n\t\tconst params = new URLSearchParams({ name: hostname, type });\n\t\tconst controller = new AbortController();\n\t\tconst timeout = setTimeout(() => controller.abort(), DOH_TIMEOUT_MS);\n\t\ttry {\n\t\t\tconst response = await globalThis.fetch(`${DEFAULT_DOH_URL}?${params.toString()}`, {\n\t\t\t\theaders: { Accept: \"application/dns-json\" },\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`DoH lookup failed: ${response.status}`);\n\t\t\t}\n\t\t\tconst raw = await response.json();\n\t\t\tconst body = parseDohResponse(raw);\n\t\t\t// NXDOMAIN (3) is a legitimate \"does not exist\" — treat as empty.\n\t\t\t// Any other non-zero status (SERVFAIL=2, REFUSED=5, etc.) is\n\t\t\t// ambiguous and could be a split-view attacker hiding records\n\t\t\t// from our resolver. Fail closed.\n\t\t\tif (body.Status === 3) return [];\n\t\t\tif (body.Status !== 0) {\n\t\t\t\tthrow new Error(`DoH ${type} lookup failed: rcode=${body.Status}`);\n\t\t\t}\n\t\t\t// DoH Answer arrays often include CNAME records alongside A/AAAA\n\t\t\t// records. Their `data` is a hostname, not an IP. Filter to just\n\t\t\t// IP literals so isPrivateIp sees real addresses.\n\t\t\treturn body.Answer.map((a) => a.data).filter(isIpLiteral);\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t}\n\t}\n\n\tconst [a, aaaa] = await Promise.all([query(\"A\"), query(\"AAAA\")]);\n\treturn [...a, ...aaaa];\n};\n\n/**\n * Validate a URL and resolve its hostname to check the actual IPs against\n * the private-range blocklist. This catches DNS rebinding attacks using\n * attacker-controlled domains that publicly resolve to private addresses,\n * and wildcard DNS services like nip.io used by exploit tooling.\n *\n * Runs `validateExternalUrl` first for cheap pre-flight checks (scheme,\n * literal IP, known-bad hostnames). Then resolves the hostname and rejects\n * if ANY returned address is private.\n *\n * Fails closed: if resolution fails or returns no records, throws SsrfError.\n *\n * **Caveats.** This does NOT fully close the TOCTOU between check and\n * connect. Attacks that still work against this layer include:\n *\n * - TTL=0 rebind: authoritative server returns public IP to the check, then\n * private IP to the subsequent fetch() a few milliseconds later.\n * - Split-view via EDNS Client Subnet or source-IP inspection: the\n * authoritative server returns public IP to Cloudflare's DoH resolver and\n * private IP to the victim's own resolver (used by fetch()).\n * - Host-file overrides or split-horizon corporate DNS on self-hosted Node.\n * - Attacker-controlled rebinding services the caller has allowlisted.\n *\n * The only complete defense is a network-layer egress firewall. On\n * Cloudflare Workers, the platform fetch pipeline provides most of that.\n * On self-hosted Node, operators must restrict egress themselves.\n */\nexport async function resolveAndValidateExternalUrl(\n\turl: string,\n\toptions?: { resolver?: DnsResolver },\n): Promise<URL> {\n\tconst parsed = validateExternalUrl(url);\n\n\t// Strip brackets from IPv6 hostnames\n\tconst hostname = parsed.hostname.replace(IPV6_BRACKET_PATTERN, \"\");\n\n\t// If the hostname is already an IP literal, validateExternalUrl has\n\t// already checked it against the private-range list. Skip DNS.\n\tif (isIpLiteral(hostname)) {\n\t\treturn parsed;\n\t}\n\n\tconst resolver = options?.resolver ?? defaultResolver ?? cloudflareDohResolver;\n\n\tlet addresses: string[];\n\ttry {\n\t\taddresses = await resolver(hostname);\n\t} catch (error) {\n\t\tthrow new SsrfError(\n\t\t\t`Could not resolve hostname: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\tif (addresses.length === 0) {\n\t\tthrow new SsrfError(\"Hostname resolved to no addresses\");\n\t}\n\n\tfor (const ip of addresses) {\n\t\tif (isPrivateIp(ip)) {\n\t\t\tthrow new SsrfError(\"Hostname resolves to a private IP address\");\n\t\t}\n\t}\n\n\treturn parsed;\n}\n\n/** True when a string looks like an IPv4 or IPv6 literal. */\nfunction isIpLiteral(host: string): boolean {\n\tif (parseIpv4(host) !== null) return true;\n\t// Very loose IPv6 heuristic — matches anything with a colon, which is\n\t// never valid in DNS hostnames, so this is safe.\n\treturn host.includes(\":\");\n}\n\n/**\n * Fetch a URL with SSRF protection on redirects.\n *\n * Uses `redirect: \"manual\"` to intercept redirects and re-validate each\n * redirect target against SSRF rules before following it. This prevents\n * an attacker from setting up an allowed external URL that redirects to\n * an internal IP (e.g. 169.254.169.254 for cloud metadata).\n *\n * @throws SsrfError if the initial URL or any redirect target is internal\n */\n/** Headers that must be stripped when a redirect crosses origins */\nconst CREDENTIAL_HEADERS = [\"authorization\", \"cookie\", \"proxy-authorization\"];\n\nexport async function ssrfSafeFetch(\n\turl: string,\n\tinit?: RequestInit,\n\toptions?: { resolver?: DnsResolver },\n): Promise<Response> {\n\tlet currentUrl = url;\n\tlet currentInit = init;\n\n\tfor (let i = 0; i <= MAX_REDIRECTS; i++) {\n\t\tawait resolveAndValidateExternalUrl(currentUrl, options);\n\n\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t...currentInit,\n\t\t\tredirect: \"manual\",\n\t\t});\n\n\t\t// Not a redirect -- return directly\n\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\treturn response;\n\t\t}\n\n\t\t// Extract redirect target\n\t\tconst location = response.headers.get(\"Location\");\n\t\tif (!location) {\n\t\t\treturn response;\n\t\t}\n\n\t\t// Resolve relative redirects against the current URL\n\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t// Strip credential headers on cross-origin redirects\n\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t}\n\t}\n\n\tthrow new SsrfError(`Too many redirects (max ${MAX_REDIRECTS})`);\n}\n\n/**\n * Return a copy of init with credential headers removed.\n */\nexport function stripCredentialHeaders(init: RequestInit): RequestInit {\n\tif (!init.headers) return init;\n\n\tconst headers = new Headers(init.headers);\n\tfor (const name of CREDENTIAL_HEADERS) {\n\t\theaders.delete(name);\n\t}\n\n\treturn { ...init, headers };\n}\n"],"mappings":";;;;;;;AAOA,MAAM,kCAAkC;AACxC,MAAM,+BAA+B;AACrC,MAAM,8BAA8B;AACpC,MAAM,+BACL;;;;;;;AAQD,MAAM,8BAA8B;;;;;;;AAQpC,MAAM,oBAAoB;AAE1B,MAAM,uBAAuB;;AAG7B,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;;AAG5B,MAAM,uBAAuB;;;;;;;;;;;AAY7B,MAAM,mBAA0D;CAE/D;EAAE,OAAO,SAAS,KAAK,GAAG,GAAG,EAAE;EAAE,KAAK,SAAS,KAAK,KAAK,KAAK,IAAI;EAAE;CAEpE;EAAE,OAAO,SAAS,IAAI,GAAG,GAAG,EAAE;EAAE,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;EAAE;CAElE;EAAE,OAAO,SAAS,KAAK,IAAI,GAAG,EAAE;EAAE,KAAK,SAAS,KAAK,IAAI,KAAK,IAAI;EAAE;CAEpE;EAAE,OAAO,SAAS,KAAK,KAAK,GAAG,EAAE;EAAE,KAAK,SAAS,KAAK,KAAK,KAAK,IAAI;EAAE;CAEtE;EAAE,OAAO,SAAS,KAAK,KAAK,GAAG,EAAE;EAAE,KAAK,SAAS,KAAK,KAAK,KAAK,IAAI;EAAE;CAEtE;EAAE,OAAO,SAAS,GAAG,GAAG,GAAG,EAAE;EAAE,KAAK,SAAS,GAAG,KAAK,KAAK,IAAI;EAAE;CAChE;AAOD,MAAM,oBAAoB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA,CAAC;;;;;;;;;AAUF,MAAM,4BAA4B;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;;AAGD,MAAM,kBAAkB,IAAI,IAAI,CAAC,SAAS,SAAS,CAAC;AAEpD,SAAS,SAAS,GAAW,GAAW,GAAW,GAAmB;AACrE,SAAS,KAAK,KAAO,KAAK,KAAO,KAAK,IAAK,OAAO;;AAGnD,SAAS,UAAU,IAA2B;CAC7C,MAAM,QAAQ,GAAG,MAAM,IAAI;AAC3B,KAAI,MAAM,WAAW,EAAG,QAAO;CAE/B,MAAM,OAAO,MAAM,IAAI,OAAO;AAC9B,KAAI,KAAK,MAAM,MAAM,MAAM,EAAE,IAAI,IAAI,KAAK,IAAI,IAAI,CAAE,QAAO;AAE3D,QAAO,SAAS,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG;;;;;;;;;;;AAYpD,SAAgB,0BAA0B,IAA2B;CAEpE,IAAI,QAAQ,GAAG,MAAM,6BAA6B;AAClD,KAAI,CAAC,MAEJ,SAAQ,GAAG,MAAM,4BAA4B;AAE9C,KAAI,CAAC,MAEJ,SAAQ,GAAG,MAAM,6BAA6B;AAE/C,KAAI,CAAC,MAEJ,SAAQ,GAAG,MAAM,4BAA4B;AAE9C,KAAI,CAAC,MAEJ,SAAQ,GAAG,MAAM,kBAAkB;AAEpC,KAAI,OAAO;EACV,MAAM,OAAO,SAAS,MAAM,MAAM,IAAI,GAAG;EACzC,MAAM,MAAM,SAAS,MAAM,MAAM,IAAI,GAAG;AACxC,SAAO,GAAI,QAAQ,IAAK,IAAK,GAAG,OAAO,IAAK,GAAI,OAAO,IAAK,IAAK,GAAG,MAAM;;AAE3E,QAAO;;AAGR,SAAS,YAAY,IAAqB;CAIzC,MAAM,aAAa,GAAG,aAAa;AAGnC,KAAI,eAAe,SAAS,eAAe,mBAAoB,QAAO;CAItE,MAAM,UAAU,0BAA0B,WAAW;AACrD,KAAI,QAAS,QAAO,YAAY,QAAQ;CAGxC,MAAM,UAAU,WAAW,MAAM,gCAAgC;CAGjE,MAAM,MAAM,UAFC,UAAU,QAAQ,KAAK,WAET;AAC3B,KAAI,QAAQ,KAKX,QACC,WAAW,WAAW,QAAQ,IAC9B,oBAAoB,KAAK,WAAW,IACpC,oBAAoB,KAAK,WAAW;AAItC,QAAO,iBAAiB,MAAM,UAAU,OAAO,MAAM,SAAS,OAAO,MAAM,IAAI;;;;;AAMhF,IAAa,YAAb,cAA+B,MAAM;CACpC,OAAO;CAEP,YAAY,SAAiB;AAC5B,QAAM,QAAQ;AACd,OAAK,OAAO;;;;;;;;;;;;;;;;;;;AAoBd,MAAM,gBAAgB;AAEtB,SAAgB,oBAAoB,KAAkB;CACrD,IAAI;AACJ,KAAI;AACH,WAAS,IAAI,IAAI,IAAI;SACd;AACP,QAAM,IAAI,UAAU,cAAc;;AAInC,KAAI,CAAC,gBAAgB,IAAI,OAAO,SAAS,CACxC,OAAM,IAAI,UAAU,WAAW,OAAO,SAAS,kBAAkB;CASlE,MAAM,iBALW,OAAO,SAAS,QAAQ,sBAAsB,GAAG,CAKlC,aAAa,CAAC,QAAQ,sBAAsB,GAAG;AAG/E,KAAI,kBAAkB,IAAI,eAAe,CACxC,OAAM,IAAI,UAAU,gDAAgD;AAKrE,MAAK,MAAM,UAAU,0BACpB,KAAI,mBAAmB,UAAU,eAAe,SAAS,IAAI,SAAS,CACrE,OAAM,IAAI,UAAU,uDAAuD;AAO7E,KAAI,YAAY,eAAe,CAC9B,OAAM,IAAI,UAAU,sDAAsD;AAG3E,QAAO;;;;;;;AAmBR,IAAI,kBAAsC;;AAU1C,MAAM,iBAAiB;;AAGvB,MAAM,kBAAkB;AAWxB,SAAS,YAA8B,KAAc,KAAmC;AACvF,QAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,OAAO;;;;;;;AAQ1D,SAAS,iBAAiB,KAA2B;AACpD,KAAI,CAAC,YAAY,KAAK,SAAS,IAAI,OAAO,IAAI,WAAW,SACxD,OAAM,IAAI,MAAM,oCAAoC;CAErD,MAAM,UAAuB,EAAE;AAC/B,KAAI,YAAY,KAAK,SAAS,IAAI,MAAM,QAAQ,IAAI,OAAO,EAC1D;OAAK,MAAM,SAAS,IAAI,OACvB,KAAI,YAAY,OAAO,OAAO,IAAI,OAAO,MAAM,SAAS,SACvD,SAAQ,KAAK,EAAE,MAAM,MAAM,MAAM,CAAC;;AAIrC,QAAO;EAAE,QAAQ,IAAI;EAAQ,QAAQ;EAAS;;;;;;;;;AAU/C,MAAa,wBAAqC,OAAO,aAAa;CACrE,eAAe,MAAM,MAAuC;EAC3D,MAAM,SAAS,IAAI,gBAAgB;GAAE,MAAM;GAAU;GAAM,CAAC;EAC5D,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,UAAU,iBAAiB,WAAW,OAAO,EAAE,eAAe;AACpE,MAAI;GACH,MAAM,WAAW,MAAM,WAAW,MAAM,GAAG,gBAAgB,GAAG,OAAO,UAAU,IAAI;IAClF,SAAS,EAAE,QAAQ,wBAAwB;IAC3C,QAAQ,WAAW;IACnB,CAAC;AACF,OAAI,CAAC,SAAS,GACb,OAAM,IAAI,MAAM,sBAAsB,SAAS,SAAS;GAGzD,MAAM,OAAO,iBADD,MAAM,SAAS,MAAM,CACC;AAKlC,OAAI,KAAK,WAAW,EAAG,QAAO,EAAE;AAChC,OAAI,KAAK,WAAW,EACnB,OAAM,IAAI,MAAM,OAAO,KAAK,wBAAwB,KAAK,SAAS;AAKnE,UAAO,KAAK,OAAO,KAAK,MAAM,EAAE,KAAK,CAAC,OAAO,YAAY;YAChD;AACT,gBAAa,QAAQ;;;CAIvB,MAAM,CAAC,GAAG,QAAQ,MAAM,QAAQ,IAAI,CAAC,MAAM,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC;AAChE,QAAO,CAAC,GAAG,GAAG,GAAG,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BvB,eAAsB,8BACrB,KACA,SACe;CACf,MAAM,SAAS,oBAAoB,IAAI;CAGvC,MAAM,WAAW,OAAO,SAAS,QAAQ,sBAAsB,GAAG;AAIlE,KAAI,YAAY,SAAS,CACxB,QAAO;CAGR,MAAM,WAAW,SAAS,YAAY,mBAAmB;CAEzD,IAAI;AACJ,KAAI;AACH,cAAY,MAAM,SAAS,SAAS;UAC5B,OAAO;AACf,QAAM,IAAI,UACT,+BAA+B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACrF;;AAGF,KAAI,UAAU,WAAW,EACxB,OAAM,IAAI,UAAU,oCAAoC;AAGzD,MAAK,MAAM,MAAM,UAChB,KAAI,YAAY,GAAG,CAClB,OAAM,IAAI,UAAU,4CAA4C;AAIlE,QAAO;;;AAIR,SAAS,YAAY,MAAuB;AAC3C,KAAI,UAAU,KAAK,KAAK,KAAM,QAAO;AAGrC,QAAO,KAAK,SAAS,IAAI;;;;;;;;;;;;;AAc1B,MAAM,qBAAqB;CAAC;CAAiB;CAAU;CAAsB;AAE7E,eAAsB,cACrB,KACA,MACA,SACoB;CACpB,IAAI,aAAa;CACjB,IAAI,cAAc;AAElB,MAAK,IAAI,IAAI,GAAG,KAAK,eAAe,KAAK;AACxC,QAAM,8BAA8B,YAAY,QAAQ;EAExD,MAAM,WAAW,MAAM,WAAW,MAAM,YAAY;GACnD,GAAG;GACH,UAAU;GACV,CAAC;AAGF,MAAI,SAAS,SAAS,OAAO,SAAS,UAAU,IAC/C,QAAO;EAIR,MAAM,WAAW,SAAS,QAAQ,IAAI,WAAW;AACjD,MAAI,CAAC,SACJ,QAAO;EAIR,MAAM,iBAAiB,IAAI,IAAI,WAAW,CAAC;AAC3C,eAAa,IAAI,IAAI,UAAU,WAAW,CAAC;AAI3C,MAAI,mBAHe,IAAI,IAAI,WAAW,CAAC,UAGF,YACpC,eAAc,uBAAuB,YAAY;;AAInD,OAAM,IAAI,UAAU,2BAA2B,cAAc,GAAG;;;;;AAMjE,SAAgB,uBAAuB,MAAgC;AACtE,KAAI,CAAC,KAAK,QAAS,QAAO;CAE1B,MAAM,UAAU,IAAI,QAAQ,KAAK,QAAQ;AACzC,MAAK,MAAM,QAAQ,mBAClB,SAAQ,OAAO,KAAK;AAGrB,QAAO;EAAE,GAAG;EAAM;EAAS"}
1
+ {"version":3,"file":"ssrf-CviKqWmq.mjs","names":[],"sources":["../src/security/ssrf.ts"],"sourcesContent":["/**\n * SSRF protection for import URLs.\n *\n * Validates that URLs don't target internal/private network addresses.\n * Applied before any fetch() call in the import pipeline.\n */\n\nconst IPV4_MAPPED_IPV6_DOTTED_PATTERN = /^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/i;\nconst IPV4_MAPPED_IPV6_HEX_PATTERN = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\nconst IPV4_TRANSLATED_HEX_PATTERN = /^::ffff:0:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\nconst IPV6_EXPANDED_MAPPED_PATTERN =\n\t/^0{0,4}:0{0,4}:0{0,4}:0{0,4}:0{0,4}:ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\n\n/**\n * IPv4-compatible (deprecated) addresses: ::XXXX:XXXX\n *\n * The WHATWG URL parser normalizes [::127.0.0.1] to [::7f00:1] (no ffff prefix).\n * These are deprecated but still parsed, and bypass the ffff-based checks.\n */\nconst IPV4_COMPATIBLE_HEX_PATTERN = /^::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\n\n/**\n * NAT64 prefix (RFC 6052): 64:ff9b::XXXX:XXXX\n *\n * Used by NAT64 gateways to embed IPv4 addresses in IPv6.\n * [64:ff9b::127.0.0.1] normalizes to [64:ff9b::7f00:1].\n */\nconst NAT64_HEX_PATTERN = /^64:ff9b::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;\n\nconst IPV6_BRACKET_PATTERN = /^\\[|\\]$/g;\n\n/** Match fc00::/7 ULA — first byte 0xfc or 0xfd followed by any byte. */\nconst IPV6_ULA_FC_PATTERN = /^fc[0-9a-f]{2}:/;\nconst IPV6_ULA_FD_PATTERN = /^fd[0-9a-f]{2}:/;\n\n/** Strip trailing dots from an FQDN-form hostname (\"localhost.\" -> \"localhost\"). */\nconst TRAILING_DOT_PATTERN = /\\.+$/;\n\n/**\n * Private and reserved IP ranges that should never be fetched.\n *\n * Includes:\n * - Loopback (127.0.0.0/8)\n * - Private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)\n * - Link-local (169.254.0.0/16)\n * - Cloud metadata (169.254.169.254 — AWS/GCP/Azure)\n * - IPv6 loopback and link-local\n */\nconst BLOCKED_PATTERNS: Array<{ start: number; end: number }> = [\n\t// 127.0.0.0/8 — loopback\n\t{ start: ip4ToNum(127, 0, 0, 0), end: ip4ToNum(127, 255, 255, 255) },\n\t// 10.0.0.0/8 — private\n\t{ start: ip4ToNum(10, 0, 0, 0), end: ip4ToNum(10, 255, 255, 255) },\n\t// 172.16.0.0/12 — private\n\t{ start: ip4ToNum(172, 16, 0, 0), end: ip4ToNum(172, 31, 255, 255) },\n\t// 192.168.0.0/16 — private\n\t{ start: ip4ToNum(192, 168, 0, 0), end: ip4ToNum(192, 168, 255, 255) },\n\t// 169.254.0.0/16 — link-local (includes cloud metadata endpoint)\n\t{ start: ip4ToNum(169, 254, 0, 0), end: ip4ToNum(169, 254, 255, 255) },\n\t// 0.0.0.0/8 — current network\n\t{ start: ip4ToNum(0, 0, 0, 0), end: ip4ToNum(0, 255, 255, 255) },\n];\n\n// Bracket-stripped form is used for lookups (validateExternalUrl strips\n// brackets from parsed.hostname before checking), so \"::1\" appears here\n// without brackets. The \"::1\" case is already covered by isPrivateIp, but\n// keeping it here makes the intent explicit and gives a clearer error\n// message for the common `http://[::1]/` form.\nconst BLOCKED_HOSTNAMES = new Set([\n\t\"localhost\",\n\t\"metadata.google.internal\",\n\t\"metadata.google\",\n\t\"::1\",\n]);\n\n/**\n * Wildcard DNS services that publicly resolve arbitrary IPs embedded in the\n * hostname. Commonly used in local dev and by SSRF exploit tooling to bypass\n * hostname-only blocklists (e.g. 127.0.0.1.nip.io -> 127.0.0.1).\n *\n * Matched case-insensitively as a suffix, so both the apex and any subdomain\n * are blocked.\n */\nconst BLOCKED_HOSTNAME_SUFFIXES = [\n\t\"nip.io\",\n\t\"sslip.io\",\n\t\"xip.io\",\n\t\"traefik.me\",\n\t\"lvh.me\",\n\t\"localtest.me\",\n];\n\n/** Blocked URL schemes */\nconst ALLOWED_SCHEMES = new Set([\"http:\", \"https:\"]);\n\nfunction ip4ToNum(a: number, b: number, c: number, d: number): number {\n\treturn ((a << 24) | (b << 16) | (c << 8) | d) >>> 0;\n}\n\nfunction parseIpv4(ip: string): number | null {\n\tconst parts = ip.split(\".\");\n\tif (parts.length !== 4) return null;\n\n\tconst nums = parts.map(Number);\n\tif (nums.some((n) => isNaN(n) || n < 0 || n > 255)) return null;\n\n\treturn ip4ToNum(nums[0], nums[1], nums[2], nums[3]);\n}\n\n/**\n * Convert IPv4-mapped/translated IPv6 addresses from hex form back to IPv4.\n *\n * The WHATWG URL parser normalizes dotted-decimal to hex:\n * [::ffff:127.0.0.1] -> [::ffff:7f00:1]\n * [::ffff:169.254.169.254] -> [::ffff:a9fe:a9fe]\n *\n * Without this conversion, the hex forms bypass isPrivateIp() regex checks.\n */\nexport function normalizeIPv6MappedToIPv4(ip: string): string | null {\n\t// Match hex-form IPv4-mapped IPv6: ::ffff:XXXX:XXXX\n\tlet match = ip.match(IPV4_MAPPED_IPV6_HEX_PATTERN);\n\tif (!match) {\n\t\t// Match IPv4-translated (RFC 6052): ::ffff:0:XXXX:XXXX\n\t\tmatch = ip.match(IPV4_TRANSLATED_HEX_PATTERN);\n\t}\n\tif (!match) {\n\t\t// Match fully expanded form: 0000:0000:0000:0000:0000:ffff:XXXX:XXXX\n\t\tmatch = ip.match(IPV6_EXPANDED_MAPPED_PATTERN);\n\t}\n\tif (!match) {\n\t\t// Match IPv4-compatible (deprecated) form: ::XXXX:XXXX (no ffff prefix)\n\t\tmatch = ip.match(IPV4_COMPATIBLE_HEX_PATTERN);\n\t}\n\tif (!match) {\n\t\t// Match NAT64 prefix (RFC 6052): 64:ff9b::XXXX:XXXX\n\t\tmatch = ip.match(NAT64_HEX_PATTERN);\n\t}\n\tif (match) {\n\t\tconst high = parseInt(match[1] ?? \"\", 16);\n\t\tconst low = parseInt(match[2] ?? \"\", 16);\n\t\treturn `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;\n\t}\n\treturn null;\n}\n\nfunction isPrivateIp(ip: string): boolean {\n\t// Normalize IPv6 strings to lowercase. `new URL().hostname` already\n\t// lowercases, but resolver output (from DoH or an injected resolver) may\n\t// not. Without this, \"FE80::1\" bypasses the link-local check.\n\tconst normalized = ip.toLowerCase();\n\n\t// Handle IPv6 loopback\n\tif (normalized === \"::1\" || normalized === \"::ffff:127.0.0.1\") return true;\n\n\t// Handle IPv4-mapped IPv6 in hex form (WHATWG URL parser normalizes to this)\n\t// e.g. ::ffff:7f00:1 -> 127.0.0.1, ::ffff:a9fe:a9fe -> 169.254.169.254\n\tconst hexIpv4 = normalizeIPv6MappedToIPv4(normalized);\n\tif (hexIpv4) return isPrivateIp(hexIpv4);\n\n\t// Handle IPv4-mapped IPv6 in dotted-decimal form\n\tconst v4Match = normalized.match(IPV4_MAPPED_IPV6_DOTTED_PATTERN);\n\tconst ipv4 = v4Match ? v4Match[1] : normalized;\n\n\tconst num = parseIpv4(ipv4);\n\tif (num === null) {\n\t\t// If we can't parse it, block IPv6 addresses that look internal.\n\t\t// fc00::/7 is Unique Local (first byte 0xfc or 0xfd), fe80::/10 is\n\t\t// link-local. Only match when followed by hex digit + colon to avoid\n\t\t// collisions with hypothetical non-address strings.\n\t\treturn (\n\t\t\tnormalized.startsWith(\"fe80:\") ||\n\t\t\tIPV6_ULA_FC_PATTERN.test(normalized) ||\n\t\t\tIPV6_ULA_FD_PATTERN.test(normalized)\n\t\t);\n\t}\n\n\treturn BLOCKED_PATTERNS.some((range) => num >= range.start && num <= range.end);\n}\n\n/**\n * Error thrown when SSRF protection blocks a URL.\n */\nexport class SsrfError extends Error {\n\tcode = \"SSRF_BLOCKED\" as const;\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"SsrfError\";\n\t}\n}\n\n/**\n * Validate that a URL is safe to fetch (not targeting internal networks).\n *\n * Checks:\n * 1. URL is well-formed with http/https scheme\n * 2. Hostname is not a known internal name (localhost, metadata endpoints)\n * 3. If hostname is an IP literal, it's not in a private range\n *\n * Note: DNS rebinding attacks are not fully mitigated (hostname could resolve\n * to a private IP). Full protection requires resolving DNS and checking the IP\n * before connecting, which needs a custom fetch implementation. This covers\n * the most common SSRF vectors.\n *\n * @throws SsrfError if the URL targets an internal address\n */\n/** Maximum number of redirects to follow in ssrfSafeFetch */\nconst MAX_REDIRECTS = 5;\n\nexport function validateExternalUrl(url: string): URL {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\tthrow new SsrfError(\"Invalid URL\");\n\t}\n\n\t// Only allow http/https\n\tif (!ALLOWED_SCHEMES.has(parsed.protocol)) {\n\t\tthrow new SsrfError(`Scheme '${parsed.protocol}' is not allowed`);\n\t}\n\n\t// Strip brackets from IPv6 hostname\n\tconst hostname = parsed.hostname.replace(IPV6_BRACKET_PATTERN, \"\");\n\n\t// Normalize the hostname for blocklist matching: lowercase + strip any\n\t// trailing dots. WHATWG preserves trailing dots on .hostname, so without\n\t// this normalization \"localhost.\" and \"nip.io.\" bypass the checks.\n\tconst normalizedHost = hostname.toLowerCase().replace(TRAILING_DOT_PATTERN, \"\");\n\n\t// Check against known internal hostnames\n\tif (BLOCKED_HOSTNAMES.has(normalizedHost)) {\n\t\tthrow new SsrfError(\"URLs targeting internal hosts are not allowed\");\n\t}\n\n\t// Check against wildcard DNS services used by SSRF tooling to bypass\n\t// hostname-only checks. Match the apex and any subdomain.\n\tfor (const suffix of BLOCKED_HOSTNAME_SUFFIXES) {\n\t\tif (normalizedHost === suffix || normalizedHost.endsWith(`.${suffix}`)) {\n\t\t\tthrow new SsrfError(\"URLs targeting wildcard DNS services are not allowed\");\n\t\t}\n\t}\n\n\t// Check if hostname is an IP address in a private range. Use the\n\t// normalized form so \"127.0.0.1..\" and friends don't bypass parseIpv4\n\t// (which rejects extra trailing dots).\n\tif (isPrivateIp(normalizedHost)) {\n\t\tthrow new SsrfError(\"URLs targeting private IP addresses are not allowed\");\n\t}\n\n\treturn parsed;\n}\n\n// ---------------------------------------------------------------------------\n// DNS-aware validation\n// ---------------------------------------------------------------------------\n\n/**\n * A resolver that maps a hostname to a list of IPv4/IPv6 addresses.\n * Injectable so callers can swap in OS-level DNS on Node, stub it in tests,\n * or point to a different DoH endpoint.\n */\nexport type DnsResolver = (hostname: string) => Promise<string[]>;\n\n/**\n * Module-level default resolver. Tests can swap this with a stub so fetch\n * mocks don't see unexpected DoH round-trips. Production code should leave\n * it alone.\n */\nlet defaultResolver: DnsResolver | null = null;\n\n/** Override the default DNS resolver. Returns the previous value. */\nexport function setDefaultDnsResolver(resolver: DnsResolver | null): DnsResolver | null {\n\tconst previous = defaultResolver;\n\tdefaultResolver = resolver;\n\treturn previous;\n}\n\n/** Timeout for a single DoH request, in milliseconds. */\nconst DOH_TIMEOUT_MS = 3000;\n\n/** Default DoH endpoint — Cloudflare's public resolver. */\nconst DEFAULT_DOH_URL = \"https://cloudflare-dns.com/dns-query\";\n\ninterface DohAnswer {\n\tdata: string;\n}\n\ninterface DohResponse {\n\tStatus: number;\n\tAnswer: DohAnswer[];\n}\n\nfunction hasProperty<K extends string>(obj: unknown, key: K): obj is Record<K, unknown> {\n\treturn typeof obj === \"object\" && obj !== null && key in obj;\n}\n\n/**\n * Narrow an unknown JSON body to a DohResponse shape we can read safely.\n * Throws if the body doesn't look like a DoH response — a malformed body is\n * indistinguishable from a failure and must not be silently treated as empty.\n */\nfunction parseDohResponse(raw: unknown): DohResponse {\n\tif (!hasProperty(raw, \"Status\") || typeof raw.Status !== \"number\") {\n\t\tthrow new Error(\"DoH response missing Status field\");\n\t}\n\tconst answers: DohAnswer[] = [];\n\tif (hasProperty(raw, \"Answer\") && Array.isArray(raw.Answer)) {\n\t\tfor (const entry of raw.Answer) {\n\t\t\tif (hasProperty(entry, \"data\") && typeof entry.data === \"string\") {\n\t\t\t\tanswers.push({ data: entry.data });\n\t\t\t}\n\t\t}\n\t}\n\treturn { Status: raw.Status, Answer: answers };\n}\n\n/**\n * Resolve a hostname via DNS over HTTPS (Cloudflare). Returns all A and AAAA\n * records. Works in both Workers and Node without requiring node:dns.\n *\n * Fails closed: any network error, non-2xx response, or DNS rcode != 0\n * causes a rejected promise so the calling validator treats it as a block.\n */\nexport const cloudflareDohResolver: DnsResolver = async (hostname) => {\n\tasync function query(type: \"A\" | \"AAAA\"): Promise<string[]> {\n\t\tconst params = new URLSearchParams({ name: hostname, type });\n\t\tconst controller = new AbortController();\n\t\tconst timeout = setTimeout(() => controller.abort(), DOH_TIMEOUT_MS);\n\t\ttry {\n\t\t\tconst response = await globalThis.fetch(`${DEFAULT_DOH_URL}?${params.toString()}`, {\n\t\t\t\theaders: { Accept: \"application/dns-json\" },\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`DoH lookup failed: ${response.status}`);\n\t\t\t}\n\t\t\tconst raw = await response.json();\n\t\t\tconst body = parseDohResponse(raw);\n\t\t\t// NXDOMAIN (3) is a legitimate \"does not exist\" — treat as empty.\n\t\t\t// Any other non-zero status (SERVFAIL=2, REFUSED=5, etc.) is\n\t\t\t// ambiguous and could be a split-view attacker hiding records\n\t\t\t// from our resolver. Fail closed.\n\t\t\tif (body.Status === 3) return [];\n\t\t\tif (body.Status !== 0) {\n\t\t\t\tthrow new Error(`DoH ${type} lookup failed: rcode=${body.Status}`);\n\t\t\t}\n\t\t\t// DoH Answer arrays often include CNAME records alongside A/AAAA\n\t\t\t// records. Their `data` is a hostname, not an IP. Filter to just\n\t\t\t// IP literals so isPrivateIp sees real addresses.\n\t\t\treturn body.Answer.map((a) => a.data).filter(isIpLiteral);\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t}\n\t}\n\n\tconst [a, aaaa] = await Promise.all([query(\"A\"), query(\"AAAA\")]);\n\treturn [...a, ...aaaa];\n};\n\n/**\n * Validate a URL and resolve its hostname to check the actual IPs against\n * the private-range blocklist. This catches DNS rebinding attacks using\n * attacker-controlled domains that publicly resolve to private addresses,\n * and wildcard DNS services like nip.io used by exploit tooling.\n *\n * Runs `validateExternalUrl` first for cheap pre-flight checks (scheme,\n * literal IP, known-bad hostnames). Then resolves the hostname and rejects\n * if ANY returned address is private.\n *\n * Fails closed: if resolution fails or returns no records, throws SsrfError.\n *\n * **Caveats.** This does NOT fully close the TOCTOU between check and\n * connect. Attacks that still work against this layer include:\n *\n * - TTL=0 rebind: authoritative server returns public IP to the check, then\n * private IP to the subsequent fetch() a few milliseconds later.\n * - Split-view via EDNS Client Subnet or source-IP inspection: the\n * authoritative server returns public IP to Cloudflare's DoH resolver and\n * private IP to the victim's own resolver (used by fetch()).\n * - Host-file overrides or split-horizon corporate DNS on self-hosted Node.\n * - Attacker-controlled rebinding services the caller has allowlisted.\n *\n * The only complete defense is a network-layer egress firewall. On\n * Cloudflare Workers, the platform fetch pipeline provides most of that.\n * On self-hosted Node, operators must restrict egress themselves.\n */\nexport async function resolveAndValidateExternalUrl(\n\turl: string,\n\toptions?: { resolver?: DnsResolver },\n): Promise<URL> {\n\tconst parsed = validateExternalUrl(url);\n\n\t// Strip brackets from IPv6 hostnames\n\tconst hostname = parsed.hostname.replace(IPV6_BRACKET_PATTERN, \"\");\n\n\t// If the hostname is already an IP literal, validateExternalUrl has\n\t// already checked it against the private-range list. Skip DNS.\n\tif (isIpLiteral(hostname)) {\n\t\treturn parsed;\n\t}\n\n\tconst resolver = options?.resolver ?? defaultResolver ?? cloudflareDohResolver;\n\n\tlet addresses: string[];\n\ttry {\n\t\taddresses = await resolver(hostname);\n\t} catch (error) {\n\t\tthrow new SsrfError(\n\t\t\t`Could not resolve hostname: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n\n\tif (addresses.length === 0) {\n\t\tthrow new SsrfError(\"Hostname resolved to no addresses\");\n\t}\n\n\tfor (const ip of addresses) {\n\t\tif (isPrivateIp(ip)) {\n\t\t\tthrow new SsrfError(\"Hostname resolves to a private IP address\");\n\t\t}\n\t}\n\n\treturn parsed;\n}\n\n/** True when a string looks like an IPv4 or IPv6 literal. */\nfunction isIpLiteral(host: string): boolean {\n\tif (parseIpv4(host) !== null) return true;\n\t// Very loose IPv6 heuristic — matches anything with a colon, which is\n\t// never valid in DNS hostnames, so this is safe.\n\treturn host.includes(\":\");\n}\n\n/**\n * Fetch a URL with SSRF protection on redirects.\n *\n * Uses `redirect: \"manual\"` to intercept redirects and re-validate each\n * redirect target against SSRF rules before following it. This prevents\n * an attacker from setting up an allowed external URL that redirects to\n * an internal IP (e.g. 169.254.169.254 for cloud metadata).\n *\n * @throws SsrfError if the initial URL or any redirect target is internal\n */\n/** Headers that must be stripped when a redirect crosses origins */\nconst CREDENTIAL_HEADERS = [\"authorization\", \"cookie\", \"proxy-authorization\"];\n\nexport async function ssrfSafeFetch(\n\turl: string,\n\tinit?: RequestInit,\n\toptions?: { resolver?: DnsResolver },\n): Promise<Response> {\n\tlet currentUrl = url;\n\tlet currentInit = init;\n\n\tfor (let i = 0; i <= MAX_REDIRECTS; i++) {\n\t\tawait resolveAndValidateExternalUrl(currentUrl, options);\n\n\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t...currentInit,\n\t\t\tredirect: \"manual\",\n\t\t});\n\n\t\t// Not a redirect -- return directly\n\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\treturn response;\n\t\t}\n\n\t\t// Extract redirect target\n\t\tconst location = response.headers.get(\"Location\");\n\t\tif (!location) {\n\t\t\treturn response;\n\t\t}\n\n\t\t// Resolve relative redirects against the current URL\n\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t// Strip credential headers on cross-origin redirects\n\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t}\n\t}\n\n\tthrow new SsrfError(`Too many redirects (max ${MAX_REDIRECTS})`);\n}\n\n/**\n * Return a copy of init with credential headers removed.\n */\nexport function stripCredentialHeaders(init: RequestInit): RequestInit {\n\tif (!init.headers) return init;\n\n\tconst headers = new Headers(init.headers);\n\tfor (const name of CREDENTIAL_HEADERS) {\n\t\theaders.delete(name);\n\t}\n\n\treturn { ...init, headers };\n}\n"],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ //#region src/plugins/state.ts
2
+ function toPluginStatus(value) {
3
+ if (value === "active") return "active";
4
+ return "inactive";
5
+ }
6
+ function toPluginSource(value) {
7
+ if (value === "marketplace") return "marketplace";
8
+ if (value === "registry") return "registry";
9
+ return "config";
10
+ }
11
+ /**
12
+ * Repository for plugin state in the database
13
+ */
14
+ var PluginStateRepository = class {
15
+ constructor(db) {
16
+ this.db = db;
17
+ }
18
+ /**
19
+ * Get state for a specific plugin
20
+ */
21
+ async get(pluginId) {
22
+ const row = await this.db.selectFrom("_plugin_state").selectAll().where("plugin_id", "=", pluginId).executeTakeFirst();
23
+ if (!row) return null;
24
+ return rowToPluginState(row);
25
+ }
26
+ /**
27
+ * Get all plugin states
28
+ */
29
+ async getAll() {
30
+ return (await this.db.selectFrom("_plugin_state").selectAll().execute()).map(rowToPluginState);
31
+ }
32
+ /**
33
+ * Get all marketplace-installed plugin states
34
+ */
35
+ async getMarketplacePlugins() {
36
+ return (await this.db.selectFrom("_plugin_state").selectAll().where("source", "=", "marketplace").execute()).map(rowToPluginState);
37
+ }
38
+ /**
39
+ * Get all registry-installed plugin states.
40
+ *
41
+ * The runtime's registry sync path uses this to discover which
42
+ * registry plugins should be loaded into the sandbox on this worker.
43
+ */
44
+ async getRegistryPlugins() {
45
+ return (await this.db.selectFrom("_plugin_state").selectAll().where("source", "=", "registry").execute()).map(rowToPluginState);
46
+ }
47
+ /**
48
+ * Create or update plugin state
49
+ */
50
+ async upsert(pluginId, version, status, opts) {
51
+ const now = (/* @__PURE__ */ new Date()).toISOString();
52
+ const existing = await this.get(pluginId);
53
+ if (existing) {
54
+ const updates = {
55
+ status,
56
+ version
57
+ };
58
+ if (status === "active" && existing.status !== "active") updates.activated_at = now;
59
+ else if (status === "inactive" && existing.status !== "inactive") updates.deactivated_at = now;
60
+ if (opts?.source) updates.source = opts.source;
61
+ if (opts?.marketplaceVersion !== void 0) updates.marketplace_version = opts.marketplaceVersion;
62
+ if (opts?.displayName !== void 0) updates.display_name = opts.displayName;
63
+ if (opts?.description !== void 0) updates.description = opts.description;
64
+ if (opts?.registryPublisherDid !== void 0) updates.registry_publisher_did = opts.registryPublisherDid;
65
+ if (opts?.registrySlug !== void 0) updates.registry_slug = opts.registrySlug;
66
+ if (opts?.mcpToolsEnabled !== void 0) updates.mcp_tools_enabled = opts.mcpToolsEnabled ? 1 : 0;
67
+ if (opts?.mcpToolsConsent !== void 0) updates.mcp_tools_consent = opts.mcpToolsConsent;
68
+ await this.db.updateTable("_plugin_state").set(updates).where("plugin_id", "=", pluginId).execute();
69
+ } else await this.db.insertInto("_plugin_state").values({
70
+ plugin_id: pluginId,
71
+ status,
72
+ version,
73
+ installed_at: now,
74
+ activated_at: status === "active" ? now : null,
75
+ deactivated_at: null,
76
+ data: null,
77
+ source: opts?.source ?? "config",
78
+ marketplace_version: opts?.marketplaceVersion ?? null,
79
+ display_name: opts?.displayName ?? null,
80
+ description: opts?.description ?? null,
81
+ registry_publisher_did: opts?.registryPublisherDid ?? null,
82
+ registry_slug: opts?.registrySlug ?? null,
83
+ mcp_tools_enabled: opts?.mcpToolsEnabled ? 1 : 0,
84
+ mcp_tools_consent: opts?.mcpToolsConsent ?? null
85
+ }).execute();
86
+ return await this.get(pluginId);
87
+ }
88
+ /**
89
+ * Enable a plugin
90
+ */
91
+ async enable(pluginId, version) {
92
+ return this.upsert(pluginId, version, "active");
93
+ }
94
+ /**
95
+ * Disable a plugin
96
+ */
97
+ async disable(pluginId, version) {
98
+ return this.upsert(pluginId, version, "inactive");
99
+ }
100
+ /**
101
+ * Delete plugin state
102
+ */
103
+ async delete(pluginId) {
104
+ return ((await this.db.deleteFrom("_plugin_state").where("plugin_id", "=", pluginId).executeTakeFirst()).numDeletedRows ?? 0) > 0;
105
+ }
106
+ };
107
+ function rowToPluginState(row) {
108
+ return {
109
+ pluginId: row.plugin_id,
110
+ status: toPluginStatus(row.status),
111
+ version: row.version,
112
+ installedAt: new Date(row.installed_at),
113
+ activatedAt: row.activated_at ? new Date(row.activated_at) : null,
114
+ deactivatedAt: row.deactivated_at ? new Date(row.deactivated_at) : null,
115
+ source: toPluginSource(row.source),
116
+ marketplaceVersion: row.marketplace_version ?? null,
117
+ displayName: row.display_name ?? null,
118
+ description: row.description ?? null,
119
+ registryPublisherDid: row.registry_publisher_did ?? null,
120
+ registrySlug: row.registry_slug ?? null,
121
+ mcpToolsEnabled: row.mcp_tools_enabled === 1,
122
+ mcpToolsConsent: row.mcp_tools_consent ?? null
123
+ };
124
+ }
125
+
126
+ //#endregion
127
+ export { PluginStateRepository as t };
128
+ //# sourceMappingURL=state-xxv6ZTMv.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-xxv6ZTMv.mjs","names":[],"sources":["../src/plugins/state.ts"],"sourcesContent":["/**\n * Plugin State Repository\n *\n * Database-backed storage for plugin activation state.\n * Used by the admin API to persist plugin enable/disable across restarts.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\n\nexport type PluginStatus = \"active\" | \"inactive\";\nexport type PluginSource = \"config\" | \"marketplace\" | \"registry\";\n\nfunction toPluginStatus(value: string): PluginStatus {\n\tif (value === \"active\") return \"active\";\n\treturn \"inactive\";\n}\n\nfunction toPluginSource(value: string | undefined | null): PluginSource {\n\tif (value === \"marketplace\") return \"marketplace\";\n\tif (value === \"registry\") return \"registry\";\n\treturn \"config\";\n}\n\nexport interface PluginState {\n\tpluginId: string;\n\tstatus: PluginStatus;\n\tversion: string;\n\tinstalledAt: Date;\n\tactivatedAt: Date | null;\n\tdeactivatedAt: Date | null;\n\tsource: PluginSource;\n\tmarketplaceVersion: string | null;\n\tdisplayName: string | null;\n\tdescription: string | null;\n\t/**\n\t * Publisher DID this plugin was published under. Populated only when\n\t * `source === \"registry\"`; null otherwise.\n\t */\n\tregistryPublisherDid: string | null;\n\t/**\n\t * Slug under which the plugin was published in the publisher's repo\n\t * (the rkey of the `pm.fair.package.profile` record). Populated only\n\t * when `source === \"registry\"`; null otherwise.\n\t *\n\t * The opaque `pluginId` for registry installs is derived from\n\t * `(registryPublisherDid, registrySlug)` -- see\n\t * `packages/core/src/registry/plugin-id.ts`.\n\t */\n\tregistrySlug: string | null;\n\tmcpToolsEnabled: boolean;\n\tmcpToolsConsent: string | null;\n}\n\n/**\n * Repository for plugin state in the database\n */\nexport class PluginStateRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Get state for a specific plugin\n\t */\n\tasync get(pluginId: string): Promise<PluginState | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_state\")\n\t\t\t.selectAll()\n\t\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) return null;\n\n\t\treturn rowToPluginState(row);\n\t}\n\n\t/**\n\t * Get all plugin states\n\t */\n\tasync getAll(): Promise<PluginState[]> {\n\t\tconst rows = await this.db.selectFrom(\"_plugin_state\").selectAll().execute();\n\t\treturn rows.map(rowToPluginState);\n\t}\n\n\t/**\n\t * Get all marketplace-installed plugin states\n\t */\n\tasync getMarketplacePlugins(): Promise<PluginState[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_plugin_state\")\n\t\t\t.selectAll()\n\t\t\t.where(\"source\", \"=\", \"marketplace\")\n\t\t\t.execute();\n\t\treturn rows.map(rowToPluginState);\n\t}\n\n\t/**\n\t * Get all registry-installed plugin states.\n\t *\n\t * The runtime's registry sync path uses this to discover which\n\t * registry plugins should be loaded into the sandbox on this worker.\n\t */\n\tasync getRegistryPlugins(): Promise<PluginState[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_plugin_state\")\n\t\t\t.selectAll()\n\t\t\t.where(\"source\", \"=\", \"registry\")\n\t\t\t.execute();\n\t\treturn rows.map(rowToPluginState);\n\t}\n\n\t/**\n\t * Create or update plugin state\n\t */\n\tasync upsert(\n\t\tpluginId: string,\n\t\tversion: string,\n\t\tstatus: PluginStatus,\n\t\topts?: {\n\t\t\tsource?: PluginSource;\n\t\t\tmarketplaceVersion?: string;\n\t\t\tdisplayName?: string;\n\t\t\tdescription?: string;\n\t\t\tregistryPublisherDid?: string;\n\t\t\tregistrySlug?: string;\n\t\t\tmcpToolsEnabled?: boolean;\n\t\t\tmcpToolsConsent?: string | null;\n\t\t},\n\t): Promise<PluginState> {\n\t\tconst now = new Date().toISOString();\n\t\tconst existing = await this.get(pluginId);\n\n\t\tif (existing) {\n\t\t\t// Update existing state\n\t\t\tconst updates: Record<string, string | number | null> = {\n\t\t\t\tstatus,\n\t\t\t\tversion,\n\t\t\t};\n\n\t\t\tif (status === \"active\" && existing.status !== \"active\") {\n\t\t\t\tupdates.activated_at = now;\n\t\t\t} else if (status === \"inactive\" && existing.status !== \"inactive\") {\n\t\t\t\tupdates.deactivated_at = now;\n\t\t\t}\n\n\t\t\tif (opts?.source) updates.source = opts.source;\n\t\t\tif (opts?.marketplaceVersion !== undefined) {\n\t\t\t\tupdates.marketplace_version = opts.marketplaceVersion;\n\t\t\t}\n\t\t\tif (opts?.displayName !== undefined) {\n\t\t\t\tupdates.display_name = opts.displayName;\n\t\t\t}\n\t\t\tif (opts?.description !== undefined) {\n\t\t\t\tupdates.description = opts.description;\n\t\t\t}\n\t\t\tif (opts?.registryPublisherDid !== undefined) {\n\t\t\t\tupdates.registry_publisher_did = opts.registryPublisherDid;\n\t\t\t}\n\t\t\tif (opts?.registrySlug !== undefined) {\n\t\t\t\tupdates.registry_slug = opts.registrySlug;\n\t\t\t}\n\t\t\tif (opts?.mcpToolsEnabled !== undefined) {\n\t\t\t\tupdates.mcp_tools_enabled = opts.mcpToolsEnabled ? 1 : 0;\n\t\t\t}\n\t\t\tif (opts?.mcpToolsConsent !== undefined) {\n\t\t\t\tupdates.mcp_tools_consent = opts.mcpToolsConsent;\n\t\t\t}\n\n\t\t\tawait this.db\n\t\t\t\t.updateTable(\"_plugin_state\")\n\t\t\t\t.set(updates)\n\t\t\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t\t\t.execute();\n\t\t} else {\n\t\t\t// Create new state\n\t\t\tawait this.db\n\t\t\t\t.insertInto(\"_plugin_state\")\n\t\t\t\t.values({\n\t\t\t\t\tplugin_id: pluginId,\n\t\t\t\t\tstatus,\n\t\t\t\t\tversion,\n\t\t\t\t\tinstalled_at: now,\n\t\t\t\t\tactivated_at: status === \"active\" ? now : null,\n\t\t\t\t\tdeactivated_at: null,\n\t\t\t\t\tdata: null,\n\t\t\t\t\tsource: opts?.source ?? \"config\",\n\t\t\t\t\tmarketplace_version: opts?.marketplaceVersion ?? null,\n\t\t\t\t\tdisplay_name: opts?.displayName ?? null,\n\t\t\t\t\tdescription: opts?.description ?? null,\n\t\t\t\t\tregistry_publisher_did: opts?.registryPublisherDid ?? null,\n\t\t\t\t\tregistry_slug: opts?.registrySlug ?? null,\n\t\t\t\t\tmcp_tools_enabled: opts?.mcpToolsEnabled ? 1 : 0,\n\t\t\t\t\tmcp_tools_consent: opts?.mcpToolsConsent ?? null,\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t}\n\n\t\treturn (await this.get(pluginId))!;\n\t}\n\n\t/**\n\t * Enable a plugin\n\t */\n\tasync enable(pluginId: string, version: string): Promise<PluginState> {\n\t\treturn this.upsert(pluginId, version, \"active\");\n\t}\n\n\t/**\n\t * Disable a plugin\n\t */\n\tasync disable(pluginId: string, version: string): Promise<PluginState> {\n\t\treturn this.upsert(pluginId, version, \"inactive\");\n\t}\n\n\t/**\n\t * Delete plugin state\n\t */\n\tasync delete(pluginId: string): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_state\")\n\t\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t\t.executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n}\n\n/**\n * Internal: map a `_plugin_state` row to the public `PluginState` shape.\n *\n * Kept at module scope so the three select paths (`get`, `getAll`,\n * `getMarketplacePlugins`, `getRegistryPlugins`) stay byte-identical in\n * their handling of nullable columns -- adding a new column to the table\n * means changing this function and nothing else.\n */\ninterface PluginStateRow {\n\tplugin_id: string;\n\tstatus: string;\n\tversion: string;\n\tinstalled_at: string;\n\tactivated_at: string | null;\n\tdeactivated_at: string | null;\n\tsource: string;\n\tmarketplace_version: string | null;\n\tdisplay_name: string | null;\n\tdescription: string | null;\n\tregistry_publisher_did: string | null;\n\tregistry_slug: string | null;\n\tmcp_tools_enabled: number;\n\tmcp_tools_consent: string | null;\n}\n\nfunction rowToPluginState(row: PluginStateRow): PluginState {\n\treturn {\n\t\tpluginId: row.plugin_id,\n\t\tstatus: toPluginStatus(row.status),\n\t\tversion: row.version,\n\t\tinstalledAt: new Date(row.installed_at),\n\t\tactivatedAt: row.activated_at ? new Date(row.activated_at) : null,\n\t\tdeactivatedAt: row.deactivated_at ? new Date(row.deactivated_at) : null,\n\t\tsource: toPluginSource(row.source),\n\t\tmarketplaceVersion: row.marketplace_version ?? null,\n\t\tdisplayName: row.display_name ?? null,\n\t\tdescription: row.description ?? null,\n\t\tregistryPublisherDid: row.registry_publisher_did ?? null,\n\t\tregistrySlug: row.registry_slug ?? null,\n\t\tmcpToolsEnabled: row.mcp_tools_enabled === 1,\n\t\tmcpToolsConsent: row.mcp_tools_consent ?? null,\n\t};\n}\n"],"mappings":"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"}
@@ -27,4 +27,4 @@ function isTerminalStatus(type) {
27
27
 
28
28
  //#endregion
29
29
  export { isTerminalStatus as n, REDIRECT_RULE_STATUSES as t };
30
- //# sourceMappingURL=status-D3Gy1Zc6.mjs.map
30
+ //# sourceMappingURL=status-COfycGIU.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"status-D3Gy1Zc6.mjs","names":[],"sources":["../src/redirects/status.ts"],"sourcesContent":["/**\n * Redirect rule status codes.\n *\n * A redirect rule's `type` is either a *redirect* status (issues a `Location`\n * header) or a *terminal* status (serves the status with no target). Terminal\n * statuses let editors mark a URL as intentionally gone:\n * - `410 Gone` — permanently and intentionally deleted (Google deindexes it\n * faster than a 404).\n * - `451 Unavailable For Legal Reasons`.\n */\n\n/** Statuses that issue an HTTP redirect (require a destination). */\nexport const REDIRECT_STATUSES = [301, 302, 307, 308] as const;\n\n/** Terminal statuses that serve a status with no `Location` / no destination. */\nexport const TERMINAL_STATUSES = [410, 451] as const;\n\n/** All values accepted as a redirect rule `type`. */\nexport const REDIRECT_RULE_STATUSES: readonly number[] = [\n\t...REDIRECT_STATUSES,\n\t...TERMINAL_STATUSES,\n];\n\n/** True for terminal statuses (410/451) — served directly, with no target. */\nexport function isTerminalStatus(type: number): boolean {\n\treturn (TERMINAL_STATUSES as readonly number[]).includes(type);\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,oBAAoB;CAAC;CAAK;CAAK;CAAK;CAAI;;AAGrD,MAAa,oBAAoB,CAAC,KAAK,IAAI;;AAG3C,MAAa,yBAA4C,CACxD,GAAG,mBACH,GAAG,kBACH;;AAGD,SAAgB,iBAAiB,MAAuB;AACvD,QAAQ,kBAAwC,SAAS,KAAK"}
1
+ {"version":3,"file":"status-COfycGIU.mjs","names":[],"sources":["../src/redirects/status.ts"],"sourcesContent":["/**\n * Redirect rule status codes.\n *\n * A redirect rule's `type` is either a *redirect* status (issues a `Location`\n * header) or a *terminal* status (serves the status with no target). Terminal\n * statuses let editors mark a URL as intentionally gone:\n * - `410 Gone` — permanently and intentionally deleted (Google deindexes it\n * faster than a 404).\n * - `451 Unavailable For Legal Reasons`.\n */\n\n/** Statuses that issue an HTTP redirect (require a destination). */\nexport const REDIRECT_STATUSES = [301, 302, 307, 308] as const;\n\n/** Terminal statuses that serve a status with no `Location` / no destination. */\nexport const TERMINAL_STATUSES = [410, 451] as const;\n\n/** All values accepted as a redirect rule `type`. */\nexport const REDIRECT_RULE_STATUSES: readonly number[] = [\n\t...REDIRECT_STATUSES,\n\t...TERMINAL_STATUSES,\n];\n\n/** True for terminal statuses (410/451) — served directly, with no target. */\nexport function isTerminalStatus(type: number): boolean {\n\treturn (TERMINAL_STATUSES as readonly number[]).includes(type);\n}\n"],"mappings":";;;;;;;;;;;;AAYA,MAAa,oBAAoB;CAAC;CAAK;CAAK;CAAK;CAAI;;AAGrD,MAAa,oBAAoB,CAAC,KAAK,IAAI;;AAG3C,MAAa,yBAA4C,CACxD,GAAG,mBACH,GAAG,kBACH;;AAGD,SAAgB,iBAAiB,MAAuB;AACvD,QAAQ,kBAAwC,SAAS,KAAK"}
@@ -1,4 +1,4 @@
1
- import { a as ListOptions, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-Cm4Cz-8M.mjs";
1
+ import { a as ListOptions, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-B97jg3aW.mjs";
2
2
 
3
3
  //#region src/storage/local.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { t as EmDashStorageError } from "../types-BNZsPL9z.mjs";
1
+ import { t as EmDashStorageError } from "../types-BD40g7zz.mjs";
2
2
  import mime from "mime/lite";
3
3
  import { createReadStream, existsSync } from "node:fs";
4
4
  import * as path from "node:path";