emdash 1.1.0 → 1.3.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 (1571) hide show
  1. package/dist/action-token-SK8zyN7w.mjs +26 -0
  2. package/dist/action-token-SK8zyN7w.mjs.map +1 -0
  3. package/dist/activation-Dim4YMM7.mjs +387 -0
  4. package/dist/activation-Dim4YMM7.mjs.map +1 -0
  5. package/dist/adapters-F_xorvWv.d.mts +173 -0
  6. package/dist/adapters-F_xorvWv.d.mts.map +1 -0
  7. package/dist/allowed-origins-CEug-p0t.mjs +116 -0
  8. package/dist/allowed-origins-CEug-p0t.mjs.map +1 -0
  9. package/dist/api/route-utils.d.mts +4 -4
  10. package/dist/api/route-utils.d.mts.map +1 -1
  11. package/dist/api/route-utils.mjs +48 -46
  12. package/dist/api/route-utils.mjs.map +1 -1
  13. package/dist/api/schemas/index.d.mts +2 -2
  14. package/dist/api/schemas/index.mjs +10 -10
  15. package/dist/api-D-7BNHoz.mjs +5025 -0
  16. package/dist/api-D-7BNHoz.mjs.map +1 -0
  17. package/dist/api-tokens-Bx38ZCQp.mjs +173 -0
  18. package/dist/api-tokens-Bx38ZCQp.mjs.map +1 -0
  19. package/dist/apply-T7ATgzCh.mjs +1702 -0
  20. package/dist/apply-T7ATgzCh.mjs.map +1 -0
  21. package/dist/approvals-D-m6wvGl.mjs +460 -0
  22. package/dist/approvals-D-m6wvGl.mjs.map +1 -0
  23. package/dist/artifact-fetch-DIh7iyOV.mjs +659 -0
  24. package/dist/artifact-fetch-DIh7iyOV.mjs.map +1 -0
  25. package/dist/artifacts-htGyfCSt.mjs +307 -0
  26. package/dist/artifacts-htGyfCSt.mjs.map +1 -0
  27. package/dist/astro/image-endpoint.d.mts.map +1 -1
  28. package/dist/astro/image-endpoint.mjs +26 -6
  29. package/dist/astro/image-endpoint.mjs.map +1 -1
  30. package/dist/astro/index.d.mts +14 -14
  31. package/dist/astro/index.d.mts.map +1 -1
  32. package/dist/astro/index.mjs +324 -23
  33. package/dist/astro/index.mjs.map +1 -1
  34. package/dist/astro/middleware/auth.d.mts +12 -12
  35. package/dist/astro/middleware/auth.d.mts.map +1 -1
  36. package/dist/astro/middleware/auth.mjs +18 -13
  37. package/dist/astro/middleware/auth.mjs.map +1 -1
  38. package/dist/astro/middleware/media-usage-write-fence.mjs +5 -5
  39. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  40. package/dist/astro/middleware/redirect.mjs +81 -46
  41. package/dist/astro/middleware/redirect.mjs.map +1 -1
  42. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  43. package/dist/astro/middleware/request-context.mjs +117 -56
  44. package/dist/astro/middleware/request-context.mjs.map +1 -1
  45. package/dist/astro/middleware/setup.mjs +1 -1
  46. package/dist/astro/middleware.d.mts +12 -12
  47. package/dist/astro/middleware.d.mts.map +1 -1
  48. package/dist/astro/middleware.mjs +145 -415
  49. package/dist/astro/middleware.mjs.map +1 -1
  50. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +14 -14
  51. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +14 -14
  52. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +7 -6
  53. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs.map +1 -1
  54. package/dist/astro/routes/api/admin/api-tokens/index.mjs +8 -7
  55. package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  57. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +16 -16
  58. package/dist/astro/routes/api/admin/byline-fields/index.mjs +16 -16
  59. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +16 -16
  60. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +22 -21
  61. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +22 -21
  63. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/bylines/index.mjs +22 -21
  65. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +18 -18
  67. package/dist/astro/routes/api/admin/comments/_id_.mjs +7 -7
  68. package/dist/astro/routes/api/admin/comments/bulk.mjs +17 -17
  69. package/dist/astro/routes/api/admin/comments/counts.mjs +7 -7
  70. package/dist/astro/routes/api/admin/comments/index.mjs +17 -17
  71. package/dist/astro/routes/api/admin/core-update.mjs +7 -7
  72. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
  73. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
  74. package/dist/astro/routes/api/admin/media-usage/activation.mjs +20 -20
  75. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +21 -21
  76. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +21 -21
  77. package/dist/astro/routes/api/admin/media-usage/progress.mjs +23 -22
  78. package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/media-usage/repair.mjs +29 -28
  80. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +21 -21
  82. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +21 -21
  83. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +6 -6
  84. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +6 -6
  85. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +8 -8
  86. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +8 -8
  87. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +72 -70
  88. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +7 -7
  90. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +71 -69
  91. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +71 -70
  93. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  94. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +80 -79
  95. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/index.mjs +72 -70
  97. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +7 -7
  99. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +71 -69
  100. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  101. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +79 -78
  102. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +71 -69
  104. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  105. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +71 -70
  106. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  107. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +81 -80
  108. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  109. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +10 -10
  110. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +73 -72
  112. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  113. package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs +7 -7
  114. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +72 -70
  115. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
  116. package/dist/astro/routes/api/admin/plugins/updates.mjs +71 -69
  117. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  118. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +5 -5
  119. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +71 -69
  120. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  121. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +7 -7
  122. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +71 -69
  123. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  124. package/dist/astro/routes/api/admin/transfer/_...path_.mjs +4 -4
  125. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs +38 -37
  126. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs.map +1 -1
  127. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs +38 -37
  128. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs.map +1 -1
  129. package/dist/astro/routes/api/admin/transfer/approvals/index.mjs +40 -39
  130. package/dist/astro/routes/api/admin/transfer/approvals/index.mjs.map +1 -1
  131. package/dist/astro/routes/api/admin/transfer/capabilities.mjs +40 -39
  132. package/dist/astro/routes/api/admin/transfer/capabilities.mjs.map +1 -1
  133. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs +40 -39
  134. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs.map +1 -1
  135. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs +41 -40
  136. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs.map +1 -1
  137. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs +41 -40
  138. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs.map +1 -1
  139. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs +40 -39
  140. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs.map +1 -1
  141. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs +41 -40
  142. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs.map +1 -1
  143. package/dist/astro/routes/api/admin/transfer/exports/index.mjs +42 -41
  144. package/dist/astro/routes/api/admin/transfer/exports/index.mjs.map +1 -1
  145. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs +40 -39
  146. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs.map +1 -1
  147. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs +40 -39
  148. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs.map +1 -1
  149. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs +42 -41
  150. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs.map +1 -1
  151. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs +40 -39
  152. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs.map +1 -1
  153. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs +42 -41
  154. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs.map +1 -1
  155. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs +40 -39
  156. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs.map +1 -1
  157. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs +40 -39
  158. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs.map +1 -1
  159. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs +42 -41
  160. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs.map +1 -1
  161. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs +40 -39
  162. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs.map +1 -1
  163. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs +40 -39
  164. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs.map +1 -1
  165. package/dist/astro/routes/api/admin/transfer/imports/index.mjs +42 -41
  166. package/dist/astro/routes/api/admin/transfer/imports/index.mjs.map +1 -1
  167. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  168. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +4 -4
  169. package/dist/astro/routes/api/admin/users/_id_/index.mjs +14 -14
  170. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +6 -6
  171. package/dist/astro/routes/api/admin/users/index.mjs +14 -14
  172. package/dist/astro/routes/api/auth/dev-bypass.mjs +6 -6
  173. package/dist/astro/routes/api/auth/handover.d.mts +8 -0
  174. package/dist/astro/routes/api/auth/handover.d.mts.map +1 -0
  175. package/dist/astro/routes/api/auth/handover.mjs +49 -0
  176. package/dist/astro/routes/api/auth/handover.mjs.map +1 -0
  177. package/dist/astro/routes/api/auth/invite/accept.mjs +4 -4
  178. package/dist/astro/routes/api/auth/invite/complete.mjs +19 -19
  179. package/dist/astro/routes/api/auth/invite/index.mjs +16 -16
  180. package/dist/astro/routes/api/auth/invite/register-options.mjs +17 -17
  181. package/dist/astro/routes/api/auth/logout.mjs +5 -5
  182. package/dist/astro/routes/api/auth/magic-link/send.mjs +19 -18
  183. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  184. package/dist/astro/routes/api/auth/magic-link/verify.d.mts.map +1 -1
  185. package/dist/astro/routes/api/auth/magic-link/verify.mjs +23 -17
  186. package/dist/astro/routes/api/auth/magic-link/verify.mjs.map +1 -1
  187. package/dist/astro/routes/api/auth/me.mjs +15 -15
  188. package/dist/astro/routes/api/auth/mode.mjs +5 -5
  189. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +8 -8
  190. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  191. package/dist/astro/routes/api/auth/passkey/_id_.mjs +14 -14
  192. package/dist/astro/routes/api/auth/passkey/index.mjs +4 -4
  193. package/dist/astro/routes/api/auth/passkey/options.d.mts.map +1 -1
  194. package/dist/astro/routes/api/auth/passkey/options.mjs +27 -20
  195. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  196. package/dist/astro/routes/api/auth/passkey/register/options.mjs +17 -17
  197. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +18 -18
  198. package/dist/astro/routes/api/auth/passkey/verify.mjs +19 -19
  199. package/dist/astro/routes/api/auth/signup/complete.mjs +19 -19
  200. package/dist/astro/routes/api/auth/signup/request.mjs +18 -17
  201. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  202. package/dist/astro/routes/api/auth/signup/verify.mjs +4 -4
  203. package/dist/astro/routes/api/calendar.mjs +15 -15
  204. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +12 -12
  205. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +18 -18
  206. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +5 -5
  207. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
  208. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +18 -17
  209. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  210. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +7 -10
  211. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs.map +1 -1
  212. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +20 -17
  213. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs.map +1 -1
  214. package/dist/astro/routes/api/content/_collection_/_id_/permanent.d.mts.map +1 -1
  215. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +7 -6
  216. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs.map +1 -1
  217. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +11 -11
  218. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +30 -29
  219. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  220. package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts.map +1 -1
  221. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +18 -17
  222. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  223. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +35 -34
  224. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  225. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +35 -34
  226. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  227. package/dist/astro/routes/api/content/_collection_/_id_/restore.d.mts.map +1 -1
  228. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +7 -6
  229. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs.map +1 -1
  230. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +5 -5
  231. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  232. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +19 -18
  233. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  234. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts.map +1 -1
  235. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +31 -26
  236. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  237. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +5 -5
  238. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
  239. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +18 -17
  240. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  241. package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
  242. package/dist/astro/routes/api/content/_collection_/_id_.mjs +27 -20
  243. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  244. package/dist/astro/routes/api/content/_collection_/authors.mjs +5 -5
  245. package/dist/astro/routes/api/content/_collection_/index.mjs +18 -21
  246. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  247. package/dist/astro/routes/api/content/_collection_/trash.mjs +15 -15
  248. package/dist/astro/routes/api/dashboard.d.mts.map +1 -1
  249. package/dist/astro/routes/api/dashboard.mjs +18 -12
  250. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  251. package/dist/astro/routes/api/dev/emails.mjs +5 -5
  252. package/dist/astro/routes/api/dev/scheduled-tasks.d.mts +12 -0
  253. package/dist/astro/routes/api/dev/scheduled-tasks.d.mts.map +1 -0
  254. package/dist/astro/routes/api/dev/scheduled-tasks.mjs +140 -0
  255. package/dist/astro/routes/api/dev/scheduled-tasks.mjs.map +1 -0
  256. package/dist/astro/routes/api/health.mjs +6 -6
  257. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  258. package/dist/astro/routes/api/import/probe.mjs +25 -23
  259. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  260. package/dist/astro/routes/api/import/wordpress/analyze.d.mts.map +1 -1
  261. package/dist/astro/routes/api/import/wordpress/analyze.mjs +7 -20
  262. package/dist/astro/routes/api/import/wordpress/analyze.mjs.map +1 -1
  263. package/dist/astro/routes/api/import/wordpress/execute.d.mts +15 -14
  264. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  265. package/dist/astro/routes/api/import/wordpress/execute.mjs +36 -30
  266. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  267. package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -12
  268. package/dist/astro/routes/api/import/wordpress/media.mjs +17 -17
  269. package/dist/astro/routes/api/import/wordpress/prepare.mjs +19 -17
  270. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  271. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts +8 -0
  272. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts.map +1 -1
  273. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs +67 -31
  274. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs.map +1 -1
  275. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +13 -13
  276. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +27 -26
  277. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  278. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  279. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +26 -23
  280. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  281. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +13 -13
  282. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  283. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +63 -35
  284. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  285. package/dist/astro/routes/api/manifest.mjs +9 -6
  286. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  287. package/dist/astro/routes/api/mcp.mjs +135 -101
  288. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  289. package/dist/astro/routes/api/media/_id_/confirm.d.mts.map +1 -1
  290. package/dist/astro/routes/api/media/_id_/confirm.mjs +34 -18
  291. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  292. package/dist/astro/routes/api/media/_id_/replace.mjs +15 -15
  293. package/dist/astro/routes/api/media/_id_/upload.mjs +8 -8
  294. package/dist/astro/routes/api/media/_id_/usage.mjs +31 -30
  295. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  296. package/dist/astro/routes/api/media/_id_.mjs +29 -28
  297. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  298. package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +6 -6
  299. package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
  300. package/dist/astro/routes/api/media/file/_...key_.mjs +45 -15
  301. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  302. package/dist/astro/routes/api/media/folders/_id_.mjs +16 -16
  303. package/dist/astro/routes/api/media/folders/index.mjs +16 -16
  304. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +5 -5
  305. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +5 -5
  306. package/dist/astro/routes/api/media/providers/index.mjs +5 -5
  307. package/dist/astro/routes/api/media/upload-url.mjs +40 -20
  308. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  309. package/dist/astro/routes/api/media.d.mts.map +1 -1
  310. package/dist/astro/routes/api/media.mjs +40 -29
  311. package/dist/astro/routes/api/media.mjs.map +1 -1
  312. package/dist/astro/routes/api/menus/_name_/items/_id_.d.mts.map +1 -1
  313. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +19 -18
  314. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  315. package/dist/astro/routes/api/menus/_name_/items.d.mts.map +1 -1
  316. package/dist/astro/routes/api/menus/_name_/items.mjs +18 -17
  317. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  318. package/dist/astro/routes/api/menus/_name_/reorder.d.mts.map +1 -1
  319. package/dist/astro/routes/api/menus/_name_/reorder.mjs +18 -17
  320. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  321. package/dist/astro/routes/api/menus/_name_/translations.d.mts.map +1 -1
  322. package/dist/astro/routes/api/menus/_name_/translations.mjs +18 -17
  323. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  324. package/dist/astro/routes/api/menus/_name_.d.mts.map +1 -1
  325. package/dist/astro/routes/api/menus/_name_.mjs +19 -18
  326. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  327. package/dist/astro/routes/api/menus/index.d.mts.map +1 -1
  328. package/dist/astro/routes/api/menus/index.mjs +18 -17
  329. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  330. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  331. package/dist/astro/routes/api/oauth/device/authorize.mjs +8 -8
  332. package/dist/astro/routes/api/oauth/device/code.mjs +12 -11
  333. package/dist/astro/routes/api/oauth/device/code.mjs.map +1 -1
  334. package/dist/astro/routes/api/oauth/device/token.mjs +11 -10
  335. package/dist/astro/routes/api/oauth/device/token.mjs.map +1 -1
  336. package/dist/astro/routes/api/oauth/register.mjs +8 -7
  337. package/dist/astro/routes/api/oauth/register.mjs.map +1 -1
  338. package/dist/astro/routes/api/oauth/token/refresh.mjs +8 -8
  339. package/dist/astro/routes/api/oauth/token/revoke.mjs +8 -8
  340. package/dist/astro/routes/api/oauth/token.mjs +8 -8
  341. package/dist/astro/routes/api/openapi.json.mjs +22 -22
  342. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  343. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +11 -11
  344. package/dist/astro/routes/api/redirects/404s/index.mjs +19 -19
  345. package/dist/astro/routes/api/redirects/404s/summary.mjs +19 -19
  346. package/dist/astro/routes/api/redirects/_id_.d.mts.map +1 -1
  347. package/dist/astro/routes/api/redirects/_id_.mjs +32 -23
  348. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  349. package/dist/astro/routes/api/redirects/index.d.mts.map +1 -1
  350. package/dist/astro/routes/api/redirects/index.mjs +24 -21
  351. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  352. package/dist/astro/routes/api/relations/_id_/index.mjs +35 -34
  353. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  354. package/dist/astro/routes/api/relations/index.mjs +35 -34
  355. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  356. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +5 -5
  357. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +16 -16
  358. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs +71 -69
  359. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs.map +1 -1
  360. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs +71 -69
  361. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs.map +1 -1
  362. package/dist/astro/routes/api/schema/block-types/index.mjs +71 -69
  363. package/dist/astro/routes/api/schema/block-types/index.mjs.map +1 -1
  364. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +71 -69
  365. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  366. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +71 -69
  367. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  368. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +71 -69
  369. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  370. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +71 -69
  371. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  372. package/dist/astro/routes/api/schema/collections/index.mjs +71 -69
  373. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  374. package/dist/astro/routes/api/schema/collections/reorder.mjs +71 -69
  375. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  376. package/dist/astro/routes/api/schema/index.mjs +17 -16
  377. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  378. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +71 -69
  379. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  380. package/dist/astro/routes/api/schema/orphans/index.mjs +71 -69
  381. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  382. package/dist/astro/routes/api/search/enable.mjs +19 -18
  383. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  384. package/dist/astro/routes/api/search/index.mjs +18 -17
  385. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  386. package/dist/astro/routes/api/search/rebuild.mjs +19 -18
  387. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  388. package/dist/astro/routes/api/search/stats.mjs +9 -8
  389. package/dist/astro/routes/api/search/stats.mjs.map +1 -1
  390. package/dist/astro/routes/api/search/suggest.mjs +18 -17
  391. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  392. package/dist/astro/routes/api/sections/_slug_.mjs +18 -17
  393. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  394. package/dist/astro/routes/api/sections/index.mjs +18 -17
  395. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  396. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +7 -7
  397. package/dist/astro/routes/api/settings/backups/archives/index.mjs +7 -7
  398. package/dist/astro/routes/api/settings/backups/export.mjs +7 -7
  399. package/dist/astro/routes/api/settings/backups/index.mjs +8 -8
  400. package/dist/astro/routes/api/settings/domain-notify.d.mts +8 -0
  401. package/dist/astro/routes/api/settings/domain-notify.d.mts.map +1 -0
  402. package/dist/astro/routes/api/settings/domain-notify.mjs +125 -0
  403. package/dist/astro/routes/api/settings/domain-notify.mjs.map +1 -0
  404. package/dist/astro/routes/api/settings/domain.d.mts +9 -0
  405. package/dist/astro/routes/api/settings/domain.d.mts.map +1 -0
  406. package/dist/astro/routes/api/settings/domain.mjs +64 -0
  407. package/dist/astro/routes/api/settings/domain.mjs.map +1 -0
  408. package/dist/astro/routes/api/settings/email.mjs +6 -6
  409. package/dist/astro/routes/api/settings.d.mts.map +1 -1
  410. package/dist/astro/routes/api/settings.mjs +23 -20
  411. package/dist/astro/routes/api/settings.mjs.map +1 -1
  412. package/dist/astro/routes/api/setup/admin-verify.d.mts.map +1 -1
  413. package/dist/astro/routes/api/setup/admin-verify.mjs +25 -21
  414. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  415. package/dist/astro/routes/api/setup/admin.mjs +18 -18
  416. package/dist/astro/routes/api/setup/dev-bypass.mjs +43 -41
  417. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  418. package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
  419. package/dist/astro/routes/api/setup/index.d.mts.map +1 -1
  420. package/dist/astro/routes/api/setup/index.mjs +53 -47
  421. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  422. package/dist/astro/routes/api/setup/status.mjs +6 -6
  423. package/dist/astro/routes/api/site/domain-proof.d.mts +8 -0
  424. package/dist/astro/routes/api/site/domain-proof.d.mts.map +1 -0
  425. package/dist/astro/routes/api/site/domain-proof.mjs +35 -0
  426. package/dist/astro/routes/api/site/domain-proof.mjs.map +1 -0
  427. package/dist/astro/routes/api/snapshot.mjs +8 -8
  428. package/dist/astro/routes/api/taxonomies/_name_/reorder.d.mts.map +1 -1
  429. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +24 -22
  430. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  431. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.d.mts.map +1 -1
  432. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +24 -22
  433. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  434. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts.map +1 -1
  435. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +25 -23
  436. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  437. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
  438. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +24 -22
  439. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  440. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +22 -21
  441. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -1
  442. package/dist/astro/routes/api/taxonomies/_name_.d.mts.map +1 -1
  443. package/dist/astro/routes/api/taxonomies/_name_.mjs +25 -23
  444. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
  445. package/dist/astro/routes/api/taxonomies/bulk-tag.mjs +35 -34
  446. package/dist/astro/routes/api/taxonomies/bulk-tag.mjs.map +1 -1
  447. package/dist/astro/routes/api/taxonomies/index.d.mts.map +1 -1
  448. package/dist/astro/routes/api/taxonomies/index.mjs +24 -22
  449. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  450. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  451. package/dist/astro/routes/api/typegen.mjs +6 -6
  452. package/dist/astro/routes/api/visual-editing/action-token.mjs +6 -6
  453. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +18 -18
  454. package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +4 -4
  455. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  456. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  457. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  458. package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts.map +1 -1
  459. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +17 -16
  460. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  461. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts.map +1 -1
  462. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +21 -19
  463. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  464. package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts.map +1 -1
  465. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +20 -18
  466. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  467. package/dist/astro/routes/api/widget-areas/_name_.d.mts.map +1 -1
  468. package/dist/astro/routes/api/widget-areas/_name_.mjs +10 -8
  469. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  470. package/dist/astro/routes/api/widget-areas/index.d.mts.map +1 -1
  471. package/dist/astro/routes/api/widget-areas/index.mjs +20 -18
  472. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  473. package/dist/astro/routes/api/widget-components.mjs +5 -5
  474. package/dist/astro/routes/robots.txt.d.mts.map +1 -1
  475. package/dist/astro/routes/robots.txt.mjs +38 -5
  476. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  477. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  478. package/dist/astro/routes/sitemap-_collection_.xml.mjs +33 -11
  479. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  480. package/dist/astro/routes/sitemap.xml.d.mts.map +1 -1
  481. package/dist/astro/routes/sitemap.xml.mjs +17 -11
  482. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  483. package/dist/astro/types.d.mts +32 -18
  484. package/dist/astro/types.d.mts.map +1 -1
  485. package/dist/audit-_gieLVGU.mjs +127 -0
  486. package/dist/audit-_gieLVGU.mjs.map +1 -0
  487. package/dist/auth/providers/github.d.mts +1 -1
  488. package/dist/auth/providers/google.d.mts +1 -1
  489. package/dist/auth/providers/microsoft.d.mts +1 -1
  490. package/dist/authoritative-records-CQlB3JzE.mjs +5183 -0
  491. package/dist/authoritative-records-CQlB3JzE.mjs.map +1 -0
  492. package/dist/authorize-DPFBGbE6.mjs +74 -0
  493. package/dist/authorize-DPFBGbE6.mjs.map +1 -0
  494. package/dist/backup-3FgVxiDJ.mjs +246 -0
  495. package/dist/backup-3FgVxiDJ.mjs.map +1 -0
  496. package/dist/block-types-CYcG56JB.mjs +132 -0
  497. package/dist/block-types-CYcG56JB.mjs.map +1 -0
  498. package/dist/block-values-gCX4HBSl.mjs +215 -0
  499. package/dist/block-values-gCX4HBSl.mjs.map +1 -0
  500. package/dist/byline-BR1qhyiw.mjs +909 -0
  501. package/dist/byline-BR1qhyiw.mjs.map +1 -0
  502. package/dist/byline-fields-4ClDC88c.mjs +123 -0
  503. package/dist/byline-fields-4ClDC88c.mjs.map +1 -0
  504. package/dist/byline-fields-BLXZnX7N.mjs +280 -0
  505. package/dist/byline-fields-BLXZnX7N.mjs.map +1 -0
  506. package/dist/bylines-CVOvRUL7.mjs +234 -0
  507. package/dist/bylines-CVOvRUL7.mjs.map +1 -0
  508. package/dist/bylines-DxOs9B7z.mjs +150 -0
  509. package/dist/bylines-DxOs9B7z.mjs.map +1 -0
  510. package/dist/cache-CEU9wo_h.mjs +151 -0
  511. package/dist/cache-CEU9wo_h.mjs.map +1 -0
  512. package/dist/canonical-BudtH-Fx.mjs +174 -0
  513. package/dist/canonical-BudtH-Fx.mjs.map +1 -0
  514. package/dist/capture-triggers-CT6dK9zd.mjs +362 -0
  515. package/dist/capture-triggers-CT6dK9zd.mjs.map +1 -0
  516. package/dist/challenge-store-C17BYx2-.mjs +51 -0
  517. package/dist/challenge-store-C17BYx2-.mjs.map +1 -0
  518. package/dist/checksum-CEqvfxGX.mjs +162 -0
  519. package/dist/checksum-CEqvfxGX.mjs.map +1 -0
  520. package/dist/chunks-xC4UD5-S.mjs +25 -0
  521. package/dist/chunks-xC4UD5-S.mjs.map +1 -0
  522. package/dist/cli/index.mjs +134 -118
  523. package/dist/cli/index.mjs.map +1 -1
  524. package/dist/client/cf-access.d.mts +1 -1
  525. package/dist/client/index.d.mts +122 -122
  526. package/dist/client/index.d.mts.map +1 -1
  527. package/dist/client/index.mjs +12 -10
  528. package/dist/client/index.mjs.map +1 -1
  529. package/dist/collection-slugs-cache-e09QeJQt.mjs +85 -0
  530. package/dist/collection-slugs-cache-e09QeJQt.mjs.map +1 -0
  531. package/dist/comment-UvIWDKcW.mjs +325 -0
  532. package/dist/comment-UvIWDKcW.mjs.map +1 -0
  533. package/dist/comment-reaction-dGp6_FRo.mjs +85 -0
  534. package/dist/comment-reaction-dGp6_FRo.mjs.map +1 -0
  535. package/dist/comments-pej-_BUO.mjs +195 -0
  536. package/dist/comments-pej-_BUO.mjs.map +1 -0
  537. package/dist/comments-yiRFx3w5.mjs +99 -0
  538. package/dist/comments-yiRFx3w5.mjs.map +1 -0
  539. package/dist/components-Brp__9xo.mjs +112 -0
  540. package/dist/components-Brp__9xo.mjs.map +1 -0
  541. package/dist/config-Bsm_u_oE.d.mts +6799 -0
  542. package/dist/config-Bsm_u_oE.d.mts.map +1 -0
  543. package/dist/config-CFMojMFK.d.mts +39 -0
  544. package/dist/config-CFMojMFK.d.mts.map +1 -0
  545. package/dist/config-DBU6iUQB.mjs +228 -0
  546. package/dist/config-DBU6iUQB.mjs.map +1 -0
  547. package/dist/connection-BtEN_lP9.mjs +36 -0
  548. package/dist/connection-BtEN_lP9.mjs.map +1 -0
  549. package/dist/content-DQSRZTVw.mjs +2075 -0
  550. package/dist/content-DQSRZTVw.mjs.map +1 -0
  551. package/dist/content-list-query-CYKcY6Vi.d.mts +26 -0
  552. package/dist/content-list-query-CYKcY6Vi.d.mts.map +1 -0
  553. package/dist/content-repair-DNh5--x5.mjs +442 -0
  554. package/dist/content-repair-DNh5--x5.mjs.map +1 -0
  555. package/dist/context-BYptmCN0.mjs +2090 -0
  556. package/dist/context-BYptmCN0.mjs.map +1 -0
  557. package/dist/credits-B9iORdWB.mjs +110 -0
  558. package/dist/credits-B9iORdWB.mjs.map +1 -0
  559. package/dist/cron-D7obTIfk.mjs +306 -0
  560. package/dist/cron-D7obTIfk.mjs.map +1 -0
  561. package/dist/dashboard-D0jcgBD7.mjs +167 -0
  562. package/dist/dashboard-D0jcgBD7.mjs.map +1 -0
  563. package/dist/database/migration-lock.d.mts +17 -4
  564. package/dist/database/migration-lock.d.mts.map +1 -1
  565. package/dist/database/migration-lock.mjs +32 -5
  566. package/dist/database/migration-lock.mjs.map +1 -1
  567. package/dist/db/index.d.mts +3 -3
  568. package/dist/db/index.mjs +1 -1
  569. package/dist/db/libsql-migrations.d.mts +1 -1
  570. package/dist/db/libsql-migrations.mjs +4 -4
  571. package/dist/db/libsql.d.mts +1 -1
  572. package/dist/db/postgres-migrations.d.mts +1 -1
  573. package/dist/db/postgres-migrations.mjs +4 -4
  574. package/dist/db/postgres.d.mts +1 -1
  575. package/dist/db/sqlite-migrations.d.mts +1 -1
  576. package/dist/db/sqlite-migrations.mjs +4 -4
  577. package/dist/db/sqlite.d.mts +1 -1
  578. package/dist/db/sqlite.mjs +1 -1
  579. package/dist/default-C0W-PhAp.mjs +81 -0
  580. package/dist/default-C0W-PhAp.mjs.map +1 -0
  581. package/dist/deferred-tasks-ZvUECSzI.d.mts +9 -0
  582. package/dist/deferred-tasks-ZvUECSzI.d.mts.map +1 -0
  583. package/dist/dev-typegen-EBni_pOQ.mjs +29 -0
  584. package/dist/dev-typegen-EBni_pOQ.mjs.map +1 -0
  585. package/dist/device-flow-BkFKBoAn.mjs +521 -0
  586. package/dist/device-flow-BkFKBoAn.mjs.map +1 -0
  587. package/dist/digest-DJ1tHCgy.mjs +194 -0
  588. package/dist/digest-DJ1tHCgy.mjs.map +1 -0
  589. package/dist/direct-executor-Cbxzg4qe.mjs +117 -0
  590. package/dist/direct-executor-Cbxzg4qe.mjs.map +1 -0
  591. package/dist/email-console-FFVJWb9N.mjs +50 -0
  592. package/dist/email-console-FFVJWb9N.mjs.map +1 -0
  593. package/dist/email-locale-BJ8DjTzZ.mjs +25 -0
  594. package/dist/email-locale-BJ8DjTzZ.mjs.map +1 -0
  595. package/dist/emdash-runtime-DVi7vtXC.mjs +4379 -0
  596. package/dist/emdash-runtime-DVi7vtXC.mjs.map +1 -0
  597. package/dist/entry-lock-C7VX0NGK.d.mts +4568 -0
  598. package/dist/entry-lock-C7VX0NGK.d.mts.map +1 -0
  599. package/dist/entry-lock-DLxCffFW.mjs +163 -0
  600. package/dist/entry-lock-DLxCffFW.mjs.map +1 -0
  601. package/dist/entry-lock-Df6-XhIu.mjs +1211 -0
  602. package/dist/entry-lock-Df6-XhIu.mjs.map +1 -0
  603. package/dist/env-types-C3FCVBWe.mjs +38 -0
  604. package/dist/env-types-C3FCVBWe.mjs.map +1 -0
  605. package/dist/error-CVWeIOku.mjs +90 -0
  606. package/dist/error-CVWeIOku.mjs.map +1 -0
  607. package/dist/errors-B4f4It-2.mjs +120 -0
  608. package/dist/errors-B4f4It-2.mjs.map +1 -0
  609. package/dist/errors-BOYNdIo3.mjs +479 -0
  610. package/dist/errors-BOYNdIo3.mjs.map +1 -0
  611. package/dist/escape-lsNq38PP.mjs +9 -0
  612. package/dist/escape-lsNq38PP.mjs.map +1 -0
  613. package/dist/fence-C3MUitUN.mjs +162 -0
  614. package/dist/fence-C3MUitUN.mjs.map +1 -0
  615. package/dist/field-defs-cache-Dez5iWJJ.mjs +77 -0
  616. package/dist/field-defs-cache-Dez5iWJJ.mjs.map +1 -0
  617. package/dist/field-map-Bft9LKak.mjs +83 -0
  618. package/dist/field-map-Bft9LKak.mjs.map +1 -0
  619. package/dist/fts-manager-Beg3e4bI.mjs +443 -0
  620. package/dist/fts-manager-Beg3e4bI.mjs.map +1 -0
  621. package/dist/gc-ByyAYiYE.mjs +75 -0
  622. package/dist/gc-ByyAYiYE.mjs.map +1 -0
  623. package/dist/hash-BJaeek87.mjs +37 -0
  624. package/dist/hash-BJaeek87.mjs.map +1 -0
  625. package/dist/hook-result-BrdLyOdI.d.mts +31 -0
  626. package/dist/hook-result-BrdLyOdI.d.mts.map +1 -0
  627. package/dist/hreflang-C8Jb-hrf.mjs +105 -0
  628. package/dist/hreflang-C8Jb-hrf.mjs.map +1 -0
  629. package/dist/hreflang-CY_F5OjF.d.mts +50 -0
  630. package/dist/hreflang-CY_F5OjF.d.mts.map +1 -0
  631. package/dist/http-route-dispatch-CyeW6iPv.mjs +342 -0
  632. package/dist/http-route-dispatch-CyeW6iPv.mjs.map +1 -0
  633. package/dist/identity-Cael5uTT.mjs +35 -0
  634. package/dist/identity-Cael5uTT.mjs.map +1 -0
  635. package/dist/import-BUXjdZUn.mjs +2126 -0
  636. package/dist/import-BUXjdZUn.mjs.map +1 -0
  637. package/dist/index-BB9sVisO.d.mts +623 -0
  638. package/dist/index-BB9sVisO.d.mts.map +1 -0
  639. package/dist/index.d.mts +24 -24
  640. package/dist/index.mjs +97 -95
  641. package/dist/install-finalization-3JX5qQEA.mjs +58 -0
  642. package/dist/install-finalization-3JX5qQEA.mjs.map +1 -0
  643. package/dist/json-recordset-BUjxhr77.mjs +12 -0
  644. package/dist/json-recordset-BUjxhr77.mjs.map +1 -0
  645. package/dist/json-schema-v_D_pPNG.mjs +15 -0
  646. package/dist/json-schema-v_D_pPNG.mjs.map +1 -0
  647. package/dist/keys-CifIkT0N.mjs +61 -0
  648. package/dist/keys-CifIkT0N.mjs.map +1 -0
  649. package/dist/kinds-B_Qcl5pz.mjs +974 -0
  650. package/dist/kinds-B_Qcl5pz.mjs.map +1 -0
  651. package/dist/lifecycle-7vUJeFRe.mjs +25 -0
  652. package/dist/lifecycle-7vUJeFRe.mjs.map +1 -0
  653. package/dist/load-CdAG9wz-.mjs +28 -0
  654. package/dist/load-CdAG9wz-.mjs.map +1 -0
  655. package/dist/loader-CswroUbe.mjs +1380 -0
  656. package/dist/loader-CswroUbe.mjs.map +1 -0
  657. package/dist/loops-uA4iTOzd.mjs +205 -0
  658. package/dist/loops-uA4iTOzd.mjs.map +1 -0
  659. package/dist/manifest-builder-B8zjuH3f.mjs +189 -0
  660. package/dist/manifest-builder-B8zjuH3f.mjs.map +1 -0
  661. package/dist/manifest-schema-DNb4XmTS.mjs +497 -0
  662. package/dist/manifest-schema-DNb4XmTS.mjs.map +1 -0
  663. package/dist/marketplace-CqbgeIa7.mjs +257 -0
  664. package/dist/marketplace-CqbgeIa7.mjs.map +1 -0
  665. package/dist/media/image-endpoint.d.mts +26 -2
  666. package/dist/media/image-endpoint.d.mts.map +1 -1
  667. package/dist/media/image-endpoint.mjs +73 -3
  668. package/dist/media/image-endpoint.mjs.map +1 -1
  669. package/dist/media/index.d.mts +1 -1
  670. package/dist/media/local-runtime.d.mts +14 -14
  671. package/dist/media/local-runtime.mjs +10 -5
  672. package/dist/media/local-runtime.mjs.map +1 -1
  673. package/dist/media--dJRU6i2.mjs +294 -0
  674. package/dist/media--dJRU6i2.mjs.map +1 -0
  675. package/dist/media-1WC65OCB.mjs +416 -0
  676. package/dist/media-1WC65OCB.mjs.map +1 -0
  677. package/dist/media-Bws9f18T.mjs +373 -0
  678. package/dist/media-Bws9f18T.mjs.map +1 -0
  679. package/dist/media-allowlist-BJB7MFx6.mjs +45 -0
  680. package/dist/media-allowlist-BJB7MFx6.mjs.map +1 -0
  681. package/dist/media-folders-Dn3C16B8.mjs +210 -0
  682. package/dist/media-folders-Dn3C16B8.mjs.map +1 -0
  683. package/dist/media-url-BQgKvpQ5.mjs +43 -0
  684. package/dist/media-url-BQgKvpQ5.mjs.map +1 -0
  685. package/dist/media-usage-CF0WVcLO.mjs +282 -0
  686. package/dist/media-usage-CF0WVcLO.mjs.map +1 -0
  687. package/dist/media-usage-activation-C9HwVw5w.mjs +78 -0
  688. package/dist/media-usage-activation-C9HwVw5w.mjs.map +1 -0
  689. package/dist/media-usage-work-CkLa5-nc.mjs +1036 -0
  690. package/dist/media-usage-work-CkLa5-nc.mjs.map +1 -0
  691. package/dist/media-usage-work-m3AWTmmC.mjs +151 -0
  692. package/dist/media-usage-work-m3AWTmmC.mjs.map +1 -0
  693. package/dist/menus-BCVSSp1z.mjs +753 -0
  694. package/dist/menus-BCVSSp1z.mjs.map +1 -0
  695. package/dist/menus-BSzwObhV.mjs +92 -0
  696. package/dist/menus-BSzwObhV.mjs.map +1 -0
  697. package/dist/menus-Cl4kwdt7.mjs +3721 -0
  698. package/dist/menus-Cl4kwdt7.mjs.map +1 -0
  699. package/dist/migrations/index.d.mts +1 -1
  700. package/dist/migrations/index.d.mts.map +1 -1
  701. package/dist/migrations/index.mjs +3 -3
  702. package/dist/mode-C-D3qICy.mjs +23 -0
  703. package/dist/mode-C-D3qICy.mjs.map +1 -0
  704. package/dist/node-sqlite-compat-CrKzRr0J.mjs +63 -0
  705. package/dist/node-sqlite-compat-CrKzRr0J.mjs.map +1 -0
  706. package/dist/oauth-authorization-TpEJrYB7.mjs +274 -0
  707. package/dist/oauth-authorization-TpEJrYB7.mjs.map +1 -0
  708. package/dist/oauth-clients-BT0Pg-MO.mjs +266 -0
  709. package/dist/oauth-clients-BT0Pg-MO.mjs.map +1 -0
  710. package/dist/oauth-state-store-CHMn_mwc.mjs +50 -0
  711. package/dist/oauth-state-store-CHMn_mwc.mjs.map +1 -0
  712. package/dist/oauth-user-lookup-BDgF6IVG.mjs +26 -0
  713. package/dist/oauth-user-lookup-BDgF6IVG.mjs.map +1 -0
  714. package/dist/object-cache/memory.d.mts +1 -1
  715. package/dist/options-DJWaq3Ie.d.mts +81 -0
  716. package/dist/options-DJWaq3Ie.d.mts.map +1 -0
  717. package/dist/ownership-MiyA7qKO.mjs +12 -0
  718. package/dist/ownership-MiyA7qKO.mjs.map +1 -0
  719. package/dist/page/index.d.mts +2 -2
  720. package/dist/page/index.mjs +9 -4
  721. package/dist/page/index.mjs.map +1 -1
  722. package/dist/parse-CWHOFc6z.d.mts +176 -0
  723. package/dist/parse-CWHOFc6z.d.mts.map +1 -0
  724. package/dist/parse-DgQ2NABT.mjs +81 -0
  725. package/dist/parse-DgQ2NABT.mjs.map +1 -0
  726. package/dist/passkey-config-D0kdzEao.mjs +46 -0
  727. package/dist/passkey-config-D0kdzEao.mjs.map +1 -0
  728. package/dist/paths-biDzFTY2.mjs +77 -0
  729. package/dist/paths-biDzFTY2.mjs.map +1 -0
  730. package/dist/patterns-CMNnYCgf.mjs +159 -0
  731. package/dist/patterns-CMNnYCgf.mjs.map +1 -0
  732. package/dist/placeholder-D41vk11_.d.mts +306 -0
  733. package/dist/placeholder-D41vk11_.d.mts.map +1 -0
  734. package/dist/plan-CCoqxNoU.mjs +135 -0
  735. package/dist/plan-CCoqxNoU.mjs.map +1 -0
  736. package/dist/plugin-test-runtime.d.mts +14 -14
  737. package/dist/plugin-test-runtime.d.mts.map +1 -1
  738. package/dist/plugin-test-runtime.mjs +110 -108
  739. package/dist/plugin-tool-name-DDUgN_0p.mjs +27 -0
  740. package/dist/plugin-tool-name-DDUgN_0p.mjs.map +1 -0
  741. package/dist/plugin-types.d.mts +2 -2
  742. package/dist/plugin-utils.d.mts +12 -12
  743. package/dist/plugin-utils.mjs +1 -1
  744. package/dist/plugin-utils.mjs.map +1 -1
  745. package/dist/plugins/adapt-sandbox-entry.d.mts +12 -12
  746. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  747. package/dist/plugins/adapt-sandbox-entry.mjs +9 -8
  748. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  749. package/dist/plugins/host.d.mts +5 -5
  750. package/dist/plugins/host.mjs +46 -42
  751. package/dist/plugins-COJ2L49n.mjs +261 -0
  752. package/dist/plugins-COJ2L49n.mjs.map +1 -0
  753. package/dist/policy-DLQ--Hnm.mjs +45 -0
  754. package/dist/policy-DLQ--Hnm.mjs.map +1 -0
  755. package/dist/portable-text-BH4zgkSm.mjs +311 -0
  756. package/dist/portable-text-BH4zgkSm.mjs.map +1 -0
  757. package/dist/preview-DLexxwp3.mjs +107 -0
  758. package/dist/preview-DLexxwp3.mjs.map +1 -0
  759. package/dist/protocol-79kUO7jP.d.mts +65 -0
  760. package/dist/protocol-79kUO7jP.d.mts.map +1 -0
  761. package/dist/public-plugin-api-routes-BuAMJ8nN.mjs +316 -0
  762. package/dist/public-plugin-api-routes-BuAMJ8nN.mjs.map +1 -0
  763. package/dist/public-submission-Di3TsgzX.mjs +251 -0
  764. package/dist/public-submission-Di3TsgzX.mjs.map +1 -0
  765. package/dist/public-url-Bt98vysr.mjs +101 -0
  766. package/dist/public-url-Bt98vysr.mjs.map +1 -0
  767. package/dist/query-CpnRlBh8.mjs +1156 -0
  768. package/dist/query-CpnRlBh8.mjs.map +1 -0
  769. package/dist/query-haKvLAWl.mjs +41 -0
  770. package/dist/query-haKvLAWl.mjs.map +1 -0
  771. package/dist/range-CYfeXgGK.mjs +51 -0
  772. package/dist/range-CYfeXgGK.mjs.map +1 -0
  773. package/dist/rate-limit-Xp4d0NBY.mjs +127 -0
  774. package/dist/rate-limit-Xp4d0NBY.mjs.map +1 -0
  775. package/dist/receipt-BUsQCOeM.mjs +43 -0
  776. package/dist/receipt-BUsQCOeM.mjs.map +1 -0
  777. package/dist/redirect-CQAV7mCo.mjs +19 -0
  778. package/dist/redirect-CQAV7mCo.mjs.map +1 -0
  779. package/dist/redirect-DiS_3mA4.mjs +423 -0
  780. package/dist/redirect-DiS_3mA4.mjs.map +1 -0
  781. package/dist/redirect-paths-CNYq7w80.mjs +36 -0
  782. package/dist/redirect-paths-CNYq7w80.mjs.map +1 -0
  783. package/dist/redirects-BEVSRMa2.mjs +148 -0
  784. package/dist/redirects-BEVSRMa2.mjs.map +1 -0
  785. package/dist/redirects-BHpy-VsZ.mjs +446 -0
  786. package/dist/redirects-BHpy-VsZ.mjs.map +1 -0
  787. package/dist/registry/testing.mjs +3 -3
  788. package/dist/registry-TUVaid9Z.mjs +8369 -0
  789. package/dist/registry-TUVaid9Z.mjs.map +1 -0
  790. package/dist/relations-CA_QuMTU.mjs +776 -0
  791. package/dist/relations-CA_QuMTU.mjs.map +1 -0
  792. package/dist/request-context.d.mts +1 -1
  793. package/dist/request-meta-bwZ_NsvU.mjs +141 -0
  794. package/dist/request-meta-bwZ_NsvU.mjs.map +1 -0
  795. package/dist/resolve-DwNM-CXl.mjs +101 -0
  796. package/dist/resolve-DwNM-CXl.mjs.map +1 -0
  797. package/dist/revision-BVp8S0e_.mjs +374 -0
  798. package/dist/revision-BVp8S0e_.mjs.map +1 -0
  799. package/dist/route-cache-QSOyDEw0.mjs +20 -0
  800. package/dist/route-cache-QSOyDEw0.mjs.map +1 -0
  801. package/dist/route-wire-Ca_HWCZ5.mjs +197 -0
  802. package/dist/route-wire-Ca_HWCZ5.mjs.map +1 -0
  803. package/dist/routes-BerZFrvV.mjs +272 -0
  804. package/dist/routes-BerZFrvV.mjs.map +1 -0
  805. package/dist/runner-BObHz5FC.d.mts +68 -0
  806. package/dist/runner-BObHz5FC.d.mts.map +1 -0
  807. package/dist/runner-C5NeAkBQ.mjs +6595 -0
  808. package/dist/runner-C5NeAkBQ.mjs.map +1 -0
  809. package/dist/runtime.d.mts +13 -13
  810. package/dist/runtime.mjs +3 -2
  811. package/dist/runtime.mjs.map +1 -1
  812. package/dist/schema/project-env-types.mjs +40 -36
  813. package/dist/schema/project-env-types.mjs.map +1 -1
  814. package/dist/schema-C4OBuv6Z.mjs +13 -0
  815. package/dist/schema-C4OBuv6Z.mjs.map +1 -0
  816. package/dist/schema-DJRfvM_o.mjs +815 -0
  817. package/dist/schema-DJRfvM_o.mjs.map +1 -0
  818. package/dist/scopes-BU03K7Uo.mjs +34 -0
  819. package/dist/scopes-BU03K7Uo.mjs.map +1 -0
  820. package/dist/search-DfVzks89.mjs +450 -0
  821. package/dist/search-DfVzks89.mjs.map +1 -0
  822. package/dist/sections-BMy4JVUz.mjs +346 -0
  823. package/dist/sections-BMy4JVUz.mjs.map +1 -0
  824. package/dist/seed/index.d.mts +2 -2
  825. package/dist/seed/index.mjs +38 -34
  826. package/dist/seo/index.d.mts +3 -3
  827. package/dist/seo/index.mjs +25 -21
  828. package/dist/seo/index.mjs.map +1 -1
  829. package/dist/seo-ClB-dw9w.mjs +188 -0
  830. package/dist/seo-ClB-dw9w.mjs.map +1 -0
  831. package/dist/seo-VZYOEy8f.mjs +133 -0
  832. package/dist/seo-VZYOEy8f.mjs.map +1 -0
  833. package/dist/service-Bo_usgSk.mjs +150 -0
  834. package/dist/service-Bo_usgSk.mjs.map +1 -0
  835. package/dist/session-user-DlpPdf8L.mjs +61 -0
  836. package/dist/session-user-DlpPdf8L.mjs.map +1 -0
  837. package/dist/settings-CWb84sDm.mjs +452 -0
  838. package/dist/settings-CWb84sDm.mjs.map +1 -0
  839. package/dist/settings-DS914jjE.mjs +66 -0
  840. package/dist/settings-DS914jjE.mjs.map +1 -0
  841. package/dist/settings-Dd1uivmQ.mjs +73 -0
  842. package/dist/settings-Dd1uivmQ.mjs.map +1 -0
  843. package/dist/setup-complete-BuNxAUiN.mjs +65 -0
  844. package/dist/setup-complete-BuNxAUiN.mjs.map +1 -0
  845. package/dist/setup-nonce-BnEAX0iy.mjs +25 -0
  846. package/dist/setup-nonce-BnEAX0iy.mjs.map +1 -0
  847. package/dist/site-domain-Bq5j5bVg.mjs +131 -0
  848. package/dist/site-domain-Bq5j5bVg.mjs.map +1 -0
  849. package/dist/site-settings-DRHfX7bq.mjs +50 -0
  850. package/dist/site-settings-DRHfX7bq.mjs.map +1 -0
  851. package/dist/site-url-MB685I5X.mjs +45 -0
  852. package/dist/site-url-MB685I5X.mjs.map +1 -0
  853. package/dist/sitemap-name-BTg2NJJv.mjs +26 -0
  854. package/dist/sitemap-name-BTg2NJJv.mjs.map +1 -0
  855. package/dist/slug-aVnUV4SL.mjs +16 -0
  856. package/dist/slug-aVnUV4SL.mjs.map +1 -0
  857. package/dist/slugify-bu2-jKia.mjs +22 -0
  858. package/dist/slugify-bu2-jKia.mjs.map +1 -0
  859. package/dist/snapshot-DYsG4uOq.mjs +414 -0
  860. package/dist/snapshot-DYsG4uOq.mjs.map +1 -0
  861. package/dist/ssrf-BcYjyM4M.mjs +406 -0
  862. package/dist/ssrf-BcYjyM4M.mjs.map +1 -0
  863. package/dist/stage-D8DWgPcO.mjs +662 -0
  864. package/dist/stage-D8DWgPcO.mjs.map +1 -0
  865. package/dist/staged-CD_imitH.mjs +686 -0
  866. package/dist/staged-CD_imitH.mjs.map +1 -0
  867. package/dist/state-CFbGZziM.mjs +178 -0
  868. package/dist/state-CFbGZziM.mjs.map +1 -0
  869. package/dist/status-BFNdxzKc.mjs +30 -0
  870. package/dist/status-BFNdxzKc.mjs.map +1 -0
  871. package/dist/storage/local.d.mts +2 -2
  872. package/dist/storage/local.d.mts.map +1 -1
  873. package/dist/storage/local.mjs +12 -5
  874. package/dist/storage/local.mjs.map +1 -1
  875. package/dist/storage/s3.d.mts +4 -2
  876. package/dist/storage/s3.d.mts.map +1 -1
  877. package/dist/storage/s3.mjs +56 -9
  878. package/dist/storage/s3.mjs.map +1 -1
  879. package/dist/storage-query-DRyu3bSU.d.mts +1615 -0
  880. package/dist/storage-query-DRyu3bSU.d.mts.map +1 -0
  881. package/dist/tar-DFjExb9X.mjs +449 -0
  882. package/dist/tar-DFjExb9X.mjs.map +1 -0
  883. package/dist/target-CWZs2-o6.mjs +27 -0
  884. package/dist/target-CWZs2-o6.mjs.map +1 -0
  885. package/dist/taxonomies-ChnU7Mc8.mjs +697 -0
  886. package/dist/taxonomies-ChnU7Mc8.mjs.map +1 -0
  887. package/dist/taxonomies-Dd9JJv_a.mjs +939 -0
  888. package/dist/taxonomies-Dd9JJv_a.mjs.map +1 -0
  889. package/dist/taxonomy-C_C_LBAU.mjs +550 -0
  890. package/dist/taxonomy-C_C_LBAU.mjs.map +1 -0
  891. package/dist/tokens-BnhSnGwJ.mjs +171 -0
  892. package/dist/tokens-BnhSnGwJ.mjs.map +1 -0
  893. package/dist/transfer-ClH6w6BV.mjs +8873 -0
  894. package/dist/transfer-ClH6w6BV.mjs.map +1 -0
  895. package/dist/transfer-WmWiR3xf.mjs +166 -0
  896. package/dist/transfer-WmWiR3xf.mjs.map +1 -0
  897. package/dist/transfer-download--ofRj9Cw.mjs +33 -0
  898. package/dist/transfer-download--ofRj9Cw.mjs.map +1 -0
  899. package/dist/transformations-Bx4lYY0Y.mjs +196 -0
  900. package/dist/transformations-Bx4lYY0Y.mjs.map +1 -0
  901. package/dist/transport-BjbqNiPI.mjs +136 -0
  902. package/dist/transport-BjbqNiPI.mjs.map +1 -0
  903. package/dist/transport-DxiXnbXk.d.mts +42 -0
  904. package/dist/transport-DxiXnbXk.d.mts.map +1 -0
  905. package/dist/trusted-proxy-BU3Nx9g1.mjs +51 -0
  906. package/dist/trusted-proxy-BU3Nx9g1.mjs.map +1 -0
  907. package/dist/turnstile-CJwvsOqw.mjs +58 -0
  908. package/dist/turnstile-CJwvsOqw.mjs.map +1 -0
  909. package/dist/types-BA3ey2QH.d.mts +87 -0
  910. package/dist/types-BA3ey2QH.d.mts.map +1 -0
  911. package/dist/types-Bwpzb5o6.d.mts +227 -0
  912. package/dist/types-Bwpzb5o6.d.mts.map +1 -0
  913. package/dist/types-CNB-dj6F.d.mts +289 -0
  914. package/dist/types-CNB-dj6F.d.mts.map +1 -0
  915. package/dist/types-CgfFGfO5.mjs +16 -0
  916. package/dist/types-CgfFGfO5.mjs.map +1 -0
  917. package/dist/types-D2NEXtgB.mjs +127 -0
  918. package/dist/types-D2NEXtgB.mjs.map +1 -0
  919. package/dist/types-DCFavOii.d.mts +916 -0
  920. package/dist/types-DCFavOii.d.mts.map +1 -0
  921. package/dist/types-DHO7Pd25.d.mts +366 -0
  922. package/dist/types-DHO7Pd25.d.mts.map +1 -0
  923. package/dist/types-DvqVxZXa.d.mts +125 -0
  924. package/dist/types-DvqVxZXa.d.mts.map +1 -0
  925. package/dist/types-QoPHm-2w.d.mts +1741 -0
  926. package/dist/types-QoPHm-2w.d.mts.map +1 -0
  927. package/dist/types-RLgeIpnF.d.mts +374 -0
  928. package/dist/types-RLgeIpnF.d.mts.map +1 -0
  929. package/dist/types-qHVygneX.mjs +77 -0
  930. package/dist/types-qHVygneX.mjs.map +1 -0
  931. package/dist/types-sUHaH7lC.mjs +37 -0
  932. package/dist/types-sUHaH7lC.mjs.map +1 -0
  933. package/dist/types-slSqujQL.d.mts +204 -0
  934. package/dist/types-slSqujQL.d.mts.map +1 -0
  935. package/dist/update-check-BxNg9T6N.mjs +161 -0
  936. package/dist/update-check-BxNg9T6N.mjs.map +1 -0
  937. package/dist/upload-attempts-DPFC4COv.mjs +32 -0
  938. package/dist/upload-attempts-DPFC4COv.mjs.map +1 -0
  939. package/dist/url-pattern-QATUE_rv.mjs +41 -0
  940. package/dist/url-pattern-QATUE_rv.mjs.map +1 -0
  941. package/dist/user-BlCTKVAm.mjs +171 -0
  942. package/dist/user-BlCTKVAm.mjs.map +1 -0
  943. package/dist/utils-TPt7w2cS.mjs +442 -0
  944. package/dist/utils-TPt7w2cS.mjs.map +1 -0
  945. package/dist/validate-DxYhJQA-.mjs +524 -0
  946. package/dist/validate-DxYhJQA-.mjs.map +1 -0
  947. package/dist/validate-emKxGC5F.d.mts +468 -0
  948. package/dist/validate-emKxGC5F.d.mts.map +1 -0
  949. package/dist/validation-Ckq4vsVg.mjs +220 -0
  950. package/dist/validation-Ckq4vsVg.mjs.map +1 -0
  951. package/dist/version-BZj18Q7G.mjs +9 -0
  952. package/dist/version-BZj18Q7G.mjs.map +1 -0
  953. package/dist/version-DHmGpcch.mjs +7 -0
  954. package/dist/version-DHmGpcch.mjs.map +1 -0
  955. package/dist/video-DytCnw3L.mjs +54 -0
  956. package/dist/video-DytCnw3L.mjs.map +1 -0
  957. package/dist/virtual-modules-DO954n7P.mjs +515 -0
  958. package/dist/virtual-modules-DO954n7P.mjs.map +1 -0
  959. package/dist/widgets-DA5XmvVC.mjs +121 -0
  960. package/dist/widgets-DA5XmvVC.mjs.map +1 -0
  961. package/dist/wxr-taxonomies-CrMonejI.d.mts +34 -0
  962. package/dist/wxr-taxonomies-CrMonejI.d.mts.map +1 -0
  963. package/dist/wxr-taxonomies-RgRG0d5e.mjs +453 -0
  964. package/dist/wxr-taxonomies-RgRG0d5e.mjs.map +1 -0
  965. package/dist/zod-generator-D0JKy94C.mjs +424 -0
  966. package/dist/zod-generator-D0JKy94C.mjs.map +1 -0
  967. package/package.json +15 -11
  968. package/src/api/authorize.ts +42 -0
  969. package/src/api/errors.ts +9 -0
  970. package/src/api/handlers/api-tokens.ts +12 -6
  971. package/src/api/handlers/content.ts +57 -8
  972. package/src/api/handlers/dashboard.ts +33 -10
  973. package/src/api/handlers/domain-move-notice.ts +94 -0
  974. package/src/api/handlers/index.ts +2 -0
  975. package/src/api/handlers/manifest.ts +6 -0
  976. package/src/api/handlers/media-usage.ts +2 -1
  977. package/src/api/handlers/media.ts +1 -0
  978. package/src/api/handlers/plugins.ts +1 -0
  979. package/src/api/handlers/revision.ts +6 -2
  980. package/src/api/handlers/seo.ts +155 -59
  981. package/src/api/handlers/settings.ts +31 -1
  982. package/src/api/handlers/site-domain.ts +159 -0
  983. package/src/api/openapi/document.ts +2 -2
  984. package/src/api/public-url.ts +1 -1
  985. package/src/api/schemas/content.ts +20 -0
  986. package/src/api/schemas/media-usage.ts +7 -1
  987. package/src/api/schemas/media.ts +12 -0
  988. package/src/api/schemas/settings.ts +2 -0
  989. package/src/api/site-url.ts +53 -5
  990. package/src/api/types.ts +4 -0
  991. package/src/astro/image-endpoint.ts +46 -4
  992. package/src/astro/integration/admin-locales.ts +168 -0
  993. package/src/astro/integration/dev-env.ts +84 -0
  994. package/src/astro/integration/dev-scheduler-bridge.ts +91 -0
  995. package/src/astro/integration/index.ts +97 -10
  996. package/src/astro/integration/routes.ts +28 -0
  997. package/src/astro/integration/runtime.ts +17 -1
  998. package/src/astro/integration/virtual-modules.ts +9 -16
  999. package/src/astro/integration/vite-config.ts +22 -16
  1000. package/src/astro/middleware/auth.ts +19 -4
  1001. package/src/astro/middleware/csp.ts +1 -0
  1002. package/src/astro/middleware/redirect.ts +94 -50
  1003. package/src/astro/middleware/request-context.ts +12 -6
  1004. package/src/astro/middleware.ts +71 -17
  1005. package/src/astro/prefetch.ts +7 -5
  1006. package/src/astro/public-plugin-api-routes.ts +10 -2
  1007. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +1 -1
  1008. package/src/astro/routes/api/auth/handover.ts +58 -0
  1009. package/src/astro/routes/api/auth/magic-link/verify.ts +11 -3
  1010. package/src/astro/routes/api/auth/passkey/options.ts +10 -2
  1011. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +2 -1
  1012. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +1 -5
  1013. package/src/astro/routes/api/content/[collection]/[id]/lock.ts +2 -2
  1014. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +2 -1
  1015. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +2 -1
  1016. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +2 -1
  1017. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +3 -2
  1018. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +5 -4
  1019. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +2 -1
  1020. package/src/astro/routes/api/content/[collection]/[id].ts +18 -6
  1021. package/src/astro/routes/api/content/[collection]/index.ts +1 -5
  1022. package/src/astro/routes/api/dashboard.ts +4 -1
  1023. package/src/astro/routes/api/dev/scheduled-tasks.ts +20 -0
  1024. package/src/astro/routes/api/import/wordpress/analyze.ts +1 -16
  1025. package/src/astro/routes/api/import/wordpress/execute.ts +27 -3
  1026. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +80 -42
  1027. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +4 -1
  1028. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +49 -5
  1029. package/src/astro/routes/api/manifest.ts +10 -0
  1030. package/src/astro/routes/api/media/[id]/confirm.ts +19 -0
  1031. package/src/astro/routes/api/media/file/[...key].ts +60 -12
  1032. package/src/astro/routes/api/media/upload-url.ts +28 -4
  1033. package/src/astro/routes/api/media.ts +15 -0
  1034. package/src/astro/routes/api/menus/[name]/items/[id].ts +3 -2
  1035. package/src/astro/routes/api/menus/[name]/items.ts +2 -1
  1036. package/src/astro/routes/api/menus/[name]/reorder.ts +2 -1
  1037. package/src/astro/routes/api/menus/[name]/translations.ts +2 -1
  1038. package/src/astro/routes/api/menus/[name].ts +3 -2
  1039. package/src/astro/routes/api/menus/index.ts +2 -1
  1040. package/src/astro/routes/api/redirects/[id].ts +15 -2
  1041. package/src/astro/routes/api/redirects/index.ts +5 -1
  1042. package/src/astro/routes/api/schema/index.ts +1 -1
  1043. package/src/astro/routes/api/settings/domain-notify.ts +76 -0
  1044. package/src/astro/routes/api/settings/domain.ts +64 -0
  1045. package/src/astro/routes/api/settings.ts +2 -1
  1046. package/src/astro/routes/api/setup/admin-verify.ts +7 -1
  1047. package/src/astro/routes/api/setup/index.ts +7 -0
  1048. package/src/astro/routes/api/site/domain-proof.ts +28 -0
  1049. package/src/astro/routes/api/taxonomies/[name]/reorder.ts +2 -1
  1050. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +2 -1
  1051. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +3 -2
  1052. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +2 -1
  1053. package/src/astro/routes/api/taxonomies/[name].ts +3 -2
  1054. package/src/astro/routes/api/taxonomies/index.ts +2 -1
  1055. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +2 -1
  1056. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +3 -2
  1057. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +2 -1
  1058. package/src/astro/routes/api/widget-areas/[name].ts +2 -1
  1059. package/src/astro/routes/api/widget-areas/index.ts +2 -1
  1060. package/src/astro/routes/robots.txt.ts +33 -2
  1061. package/src/astro/routes/sitemap-[collection].xml.ts +31 -8
  1062. package/src/astro/routes/sitemap.xml.ts +12 -10
  1063. package/src/astro/types.ts +23 -2
  1064. package/src/auth/rate-limit.ts +18 -13
  1065. package/src/auth/types.ts +11 -4
  1066. package/src/aws-sdk.d.ts +2 -1
  1067. package/src/cache/redirect-paths.ts +56 -0
  1068. package/src/cache/route-cache.ts +22 -0
  1069. package/src/cleanup.ts +26 -0
  1070. package/src/cli/commands/export-seed.ts +2 -0
  1071. package/src/cli/commands/import/wordpress.ts +3 -27
  1072. package/src/cli/commands/login.ts +35 -43
  1073. package/src/cli/commands/migrate.ts +36 -4
  1074. package/src/cli/commands/seed.ts +23 -12
  1075. package/src/cli/wxr/parser.ts +2 -2
  1076. package/src/client/index.ts +18 -3
  1077. package/src/client/portable-text.ts +21 -7
  1078. package/src/comments/public-submission.ts +15 -8
  1079. package/src/comments/query.ts +8 -2
  1080. package/src/components/CommentForm.astro +50 -10
  1081. package/src/components/Comments.astro +51 -29
  1082. package/src/components/EmDashImage.astro +6 -4
  1083. package/src/components/Gallery.astro +4 -3
  1084. package/src/components/Image.astro +4 -3
  1085. package/src/components/InlinePortableTextEditor.tsx +269 -1320
  1086. package/src/components/Video.astro +100 -0
  1087. package/src/components/index.ts +4 -1
  1088. package/src/components/inline-block-types.ts +278 -0
  1089. package/src/components/inline-bubble-menu.tsx +696 -0
  1090. package/src/components/inline-code-block.tsx +8 -0
  1091. package/src/components/inline-editor-ui.tsx +1192 -0
  1092. package/src/components/inline-image.tsx +722 -0
  1093. package/src/components/inline-media-picker.tsx +1221 -0
  1094. package/src/components/inline-slash-menu.tsx +556 -0
  1095. package/src/components/webmcp-search.ts +15 -19
  1096. package/src/components/widgets/RecentPosts.astro +57 -17
  1097. package/src/content/converters/mark-safety.ts +3 -3
  1098. package/src/content/converters/portable-text-identity.ts +1 -0
  1099. package/src/content/converters/portable-text-to-prosemirror.ts +7 -0
  1100. package/src/content/converters/prosemirror-to-portable-text.ts +16 -0
  1101. package/src/content/converters/types.ts +22 -0
  1102. package/src/content/converters/video.ts +77 -0
  1103. package/src/database/collection-cleanup.ts +30 -0
  1104. package/src/database/datetime-storage.ts +337 -60
  1105. package/src/database/migration-lock.ts +43 -7
  1106. package/src/database/migrations/runner.ts +13 -1
  1107. package/src/database/repositories/comment.ts +17 -3
  1108. package/src/database/repositories/content.ts +25 -4
  1109. package/src/database/repositories/plugin-storage.ts +137 -27
  1110. package/src/database/repositories/redirect.ts +24 -2
  1111. package/src/database/repositories/revision.ts +24 -6
  1112. package/src/database/repositories/taxonomy.ts +61 -46
  1113. package/src/database/repositories/types.ts +67 -7
  1114. package/src/emdash-runtime.ts +393 -228
  1115. package/src/import/index.ts +1 -0
  1116. package/src/import/registry.ts +17 -0
  1117. package/src/import/sources/wordpress-plugin.ts +154 -11
  1118. package/src/import/types.ts +3 -0
  1119. package/src/import/utils.ts +7 -3
  1120. package/src/index.ts +3 -0
  1121. package/src/loader.ts +170 -45
  1122. package/src/mcp/json-schema.ts +14 -0
  1123. package/src/mcp/plugin-tool-name.ts +36 -0
  1124. package/src/mcp/server.ts +74 -15
  1125. package/src/media/image-endpoint.ts +101 -1
  1126. package/src/media/responsive.ts +14 -7
  1127. package/src/media/usage/collection-deletion.ts +4 -0
  1128. package/src/media/usage/extractor.ts +19 -2
  1129. package/src/media/usage/types.ts +5 -1
  1130. package/src/migrations/direct-executor.ts +42 -12
  1131. package/src/migrations/protocol.ts +6 -2
  1132. package/src/plugin-utils.ts +1 -1
  1133. package/src/plugins/adapt-sandbox-entry.ts +16 -6
  1134. package/src/plugins/context.ts +3 -7
  1135. package/src/plugins/hooks.ts +1 -1
  1136. package/src/plugins/http-route-dispatch.ts +5 -1
  1137. package/src/plugins/manifest-schema.ts +1 -0
  1138. package/src/plugins/route-error.ts +14 -0
  1139. package/src/plugins/routes.ts +2 -2
  1140. package/src/plugins/state.ts +41 -8
  1141. package/src/plugins/types.ts +6 -0
  1142. package/src/query.ts +59 -10
  1143. package/src/redirects/cache.ts +34 -2
  1144. package/src/redirects/patterns.ts +26 -2
  1145. package/src/schema/block-values.ts +80 -17
  1146. package/src/schema/registry.ts +16 -5
  1147. package/src/schema/slug.ts +19 -0
  1148. package/src/search/query.ts +3 -1
  1149. package/src/search/text-extraction.ts +6 -1
  1150. package/src/seed/apply.ts +32 -8
  1151. package/src/seed/validate.ts +31 -0
  1152. package/src/seo/sitemap-name.ts +22 -0
  1153. package/src/settings/index.ts +20 -0
  1154. package/src/settings/types.ts +2 -0
  1155. package/src/storage/index.ts +2 -0
  1156. package/src/storage/local.ts +10 -2
  1157. package/src/storage/range.ts +54 -0
  1158. package/src/storage/s3.ts +52 -8
  1159. package/src/storage/types.ts +30 -3
  1160. package/src/ui.ts +1 -0
  1161. package/src/utils/slugify.ts +6 -1
  1162. package/src/visual-editing/toolbar.ts +126 -47
  1163. package/src/widgets/components.ts +4 -0
  1164. package/src/widgets/index.ts +74 -82
  1165. package/src/widgets/recent-posts.ts +62 -0
  1166. package/dist/action-token-AgOs1UWD.mjs +0 -26
  1167. package/dist/action-token-AgOs1UWD.mjs.map +0 -1
  1168. package/dist/activation-xomsCMJg.mjs +0 -387
  1169. package/dist/activation-xomsCMJg.mjs.map +0 -1
  1170. package/dist/adapters-DM1oyHIN.d.mts +0 -173
  1171. package/dist/adapters-DM1oyHIN.d.mts.map +0 -1
  1172. package/dist/allowed-origins-CFtudhyw.mjs +0 -116
  1173. package/dist/allowed-origins-CFtudhyw.mjs.map +0 -1
  1174. package/dist/api-CltsXhiX.mjs +0 -4980
  1175. package/dist/api-CltsXhiX.mjs.map +0 -1
  1176. package/dist/api-tokens-P3mAaQgr.mjs +0 -166
  1177. package/dist/api-tokens-P3mAaQgr.mjs.map +0 -1
  1178. package/dist/apply-Q7uvzBWW.mjs +0 -1680
  1179. package/dist/apply-Q7uvzBWW.mjs.map +0 -1
  1180. package/dist/approvals-DycwI6lZ.mjs +0 -460
  1181. package/dist/approvals-DycwI6lZ.mjs.map +0 -1
  1182. package/dist/artifact-fetch-CWxyELVI.mjs +0 -659
  1183. package/dist/artifact-fetch-CWxyELVI.mjs.map +0 -1
  1184. package/dist/artifacts-UzWbysb8.mjs +0 -307
  1185. package/dist/artifacts-UzWbysb8.mjs.map +0 -1
  1186. package/dist/audit-CCH3MA4Y.mjs +0 -127
  1187. package/dist/audit-CCH3MA4Y.mjs.map +0 -1
  1188. package/dist/authoritative-records-dSvXCfzb.mjs +0 -5172
  1189. package/dist/authoritative-records-dSvXCfzb.mjs.map +0 -1
  1190. package/dist/authorize-DeWfk--K.mjs +0 -49
  1191. package/dist/authorize-DeWfk--K.mjs.map +0 -1
  1192. package/dist/backup-CFnbdTSJ.mjs +0 -246
  1193. package/dist/backup-CFnbdTSJ.mjs.map +0 -1
  1194. package/dist/block-types-C0YFSfR7.mjs +0 -132
  1195. package/dist/block-types-C0YFSfR7.mjs.map +0 -1
  1196. package/dist/block-values-H4E0XwE8.mjs +0 -175
  1197. package/dist/block-values-H4E0XwE8.mjs.map +0 -1
  1198. package/dist/byline-BhVslYLz.mjs +0 -909
  1199. package/dist/byline-BhVslYLz.mjs.map +0 -1
  1200. package/dist/byline-fields-DHJs0Mja.mjs +0 -280
  1201. package/dist/byline-fields-DHJs0Mja.mjs.map +0 -1
  1202. package/dist/byline-fields-xdxE9kxZ.mjs +0 -123
  1203. package/dist/byline-fields-xdxE9kxZ.mjs.map +0 -1
  1204. package/dist/bylines-B_nN9liM.mjs +0 -234
  1205. package/dist/bylines-B_nN9liM.mjs.map +0 -1
  1206. package/dist/bylines-C71YACkL.mjs +0 -150
  1207. package/dist/bylines-C71YACkL.mjs.map +0 -1
  1208. package/dist/cache-aTYCL9nK.mjs +0 -133
  1209. package/dist/cache-aTYCL9nK.mjs.map +0 -1
  1210. package/dist/canonical-Y1dpjCnp.mjs +0 -174
  1211. package/dist/canonical-Y1dpjCnp.mjs.map +0 -1
  1212. package/dist/capture-triggers-C9JKoIdT.mjs +0 -362
  1213. package/dist/capture-triggers-C9JKoIdT.mjs.map +0 -1
  1214. package/dist/challenge-store-CxZxPqaw.mjs +0 -51
  1215. package/dist/challenge-store-CxZxPqaw.mjs.map +0 -1
  1216. package/dist/checksum-DtH6m5uk.mjs +0 -162
  1217. package/dist/checksum-DtH6m5uk.mjs.map +0 -1
  1218. package/dist/chunks-zlGB5IQP.mjs +0 -25
  1219. package/dist/chunks-zlGB5IQP.mjs.map +0 -1
  1220. package/dist/collection-slugs-cache-B2Gs9Jwx.mjs +0 -85
  1221. package/dist/collection-slugs-cache-B2Gs9Jwx.mjs.map +0 -1
  1222. package/dist/comment-Bu08Wvr3.mjs +0 -313
  1223. package/dist/comment-Bu08Wvr3.mjs.map +0 -1
  1224. package/dist/comment-reaction-B8SfT-yM.mjs +0 -85
  1225. package/dist/comment-reaction-B8SfT-yM.mjs.map +0 -1
  1226. package/dist/comments-BRtp992B.mjs +0 -99
  1227. package/dist/comments-BRtp992B.mjs.map +0 -1
  1228. package/dist/comments-DVUYqdyC.mjs +0 -195
  1229. package/dist/comments-DVUYqdyC.mjs.map +0 -1
  1230. package/dist/components-CQY-Sgm-.mjs +0 -108
  1231. package/dist/components-CQY-Sgm-.mjs.map +0 -1
  1232. package/dist/config-B-uePMLj.d.mts +0 -39
  1233. package/dist/config-B-uePMLj.d.mts.map +0 -1
  1234. package/dist/config-B6BDSsz2.d.mts +0 -6712
  1235. package/dist/config-B6BDSsz2.d.mts.map +0 -1
  1236. package/dist/config-BsJHiQxC.mjs +0 -228
  1237. package/dist/config-BsJHiQxC.mjs.map +0 -1
  1238. package/dist/connection-B9pIasFd.mjs +0 -36
  1239. package/dist/connection-B9pIasFd.mjs.map +0 -1
  1240. package/dist/content-Dji9vEiW.mjs +0 -2063
  1241. package/dist/content-Dji9vEiW.mjs.map +0 -1
  1242. package/dist/content-list-query-DiNwyKtd.d.mts +0 -26
  1243. package/dist/content-list-query-DiNwyKtd.d.mts.map +0 -1
  1244. package/dist/content-repair-B7wnbzhN.mjs +0 -442
  1245. package/dist/content-repair-B7wnbzhN.mjs.map +0 -1
  1246. package/dist/context-Ctcxc8Ia.mjs +0 -2026
  1247. package/dist/context-Ctcxc8Ia.mjs.map +0 -1
  1248. package/dist/credits-Bu7dlobo.mjs +0 -110
  1249. package/dist/credits-Bu7dlobo.mjs.map +0 -1
  1250. package/dist/cron-xe5XKqA-.mjs +0 -306
  1251. package/dist/cron-xe5XKqA-.mjs.map +0 -1
  1252. package/dist/dashboard-B5hVHOtf.mjs +0 -151
  1253. package/dist/dashboard-B5hVHOtf.mjs.map +0 -1
  1254. package/dist/default-DaoYyFRS.mjs +0 -81
  1255. package/dist/default-DaoYyFRS.mjs.map +0 -1
  1256. package/dist/deferred-tasks-BYcDx2o_.d.mts +0 -9
  1257. package/dist/deferred-tasks-BYcDx2o_.d.mts.map +0 -1
  1258. package/dist/dev-typegen-Dtl8AfS6.mjs +0 -29
  1259. package/dist/dev-typegen-Dtl8AfS6.mjs.map +0 -1
  1260. package/dist/device-flow-Ma5P5d2e.mjs +0 -521
  1261. package/dist/device-flow-Ma5P5d2e.mjs.map +0 -1
  1262. package/dist/digest-xIpR3o64.mjs +0 -194
  1263. package/dist/digest-xIpR3o64.mjs.map +0 -1
  1264. package/dist/direct-executor-DMWwCey3.mjs +0 -104
  1265. package/dist/direct-executor-DMWwCey3.mjs.map +0 -1
  1266. package/dist/email-console-DmqVF5J_.mjs +0 -50
  1267. package/dist/email-console-DmqVF5J_.mjs.map +0 -1
  1268. package/dist/email-locale-Cc5YSXfJ.mjs +0 -25
  1269. package/dist/email-locale-Cc5YSXfJ.mjs.map +0 -1
  1270. package/dist/emdash-runtime-BlybX9Ui.mjs +0 -4249
  1271. package/dist/emdash-runtime-BlybX9Ui.mjs.map +0 -1
  1272. package/dist/entry-lock-De1L8qUQ.mjs +0 -163
  1273. package/dist/entry-lock-De1L8qUQ.mjs.map +0 -1
  1274. package/dist/entry-lock-esmMbgI9.d.mts +0 -4551
  1275. package/dist/entry-lock-esmMbgI9.d.mts.map +0 -1
  1276. package/dist/entry-lock-sxz2PGAT.mjs +0 -1197
  1277. package/dist/entry-lock-sxz2PGAT.mjs.map +0 -1
  1278. package/dist/env-types-BkjCtN13.mjs +0 -38
  1279. package/dist/env-types-BkjCtN13.mjs.map +0 -1
  1280. package/dist/error-B0rgPgsQ.mjs +0 -90
  1281. package/dist/error-B0rgPgsQ.mjs.map +0 -1
  1282. package/dist/errors-CuHQRiBW.mjs +0 -120
  1283. package/dist/errors-CuHQRiBW.mjs.map +0 -1
  1284. package/dist/errors-DTE6C3K_.mjs +0 -470
  1285. package/dist/errors-DTE6C3K_.mjs.map +0 -1
  1286. package/dist/escape-nJW7dR0s.mjs +0 -9
  1287. package/dist/escape-nJW7dR0s.mjs.map +0 -1
  1288. package/dist/fence-CGw57S4v.mjs +0 -162
  1289. package/dist/fence-CGw57S4v.mjs.map +0 -1
  1290. package/dist/field-defs-cache-Bw8w499w.mjs +0 -77
  1291. package/dist/field-defs-cache-Bw8w499w.mjs.map +0 -1
  1292. package/dist/field-map-hn-D-SyH.mjs +0 -83
  1293. package/dist/field-map-hn-D-SyH.mjs.map +0 -1
  1294. package/dist/fts-manager-DacCYIe4.mjs +0 -443
  1295. package/dist/fts-manager-DacCYIe4.mjs.map +0 -1
  1296. package/dist/gc-BBplHrv6.mjs +0 -75
  1297. package/dist/gc-BBplHrv6.mjs.map +0 -1
  1298. package/dist/hash-C4DFoJ7Z.mjs +0 -37
  1299. package/dist/hash-C4DFoJ7Z.mjs.map +0 -1
  1300. package/dist/hook-result-DSRRxu_-.d.mts +0 -31
  1301. package/dist/hook-result-DSRRxu_-.d.mts.map +0 -1
  1302. package/dist/hreflang-CKPunvU0.mjs +0 -105
  1303. package/dist/hreflang-CKPunvU0.mjs.map +0 -1
  1304. package/dist/hreflang-egumj6WF.d.mts +0 -50
  1305. package/dist/hreflang-egumj6WF.d.mts.map +0 -1
  1306. package/dist/http-route-dispatch-DjNFZcJ2.mjs +0 -339
  1307. package/dist/http-route-dispatch-DjNFZcJ2.mjs.map +0 -1
  1308. package/dist/identity-Dfrx2_Bc.mjs +0 -35
  1309. package/dist/identity-Dfrx2_Bc.mjs.map +0 -1
  1310. package/dist/import-ROBvhbNc.mjs +0 -2021
  1311. package/dist/import-ROBvhbNc.mjs.map +0 -1
  1312. package/dist/index-B4sIdCAT.d.mts +0 -623
  1313. package/dist/index-B4sIdCAT.d.mts.map +0 -1
  1314. package/dist/install-finalization-CPM7Gged.mjs +0 -58
  1315. package/dist/install-finalization-CPM7Gged.mjs.map +0 -1
  1316. package/dist/keys-DxqxcAD4.mjs +0 -61
  1317. package/dist/keys-DxqxcAD4.mjs.map +0 -1
  1318. package/dist/kinds-BQl5gN6e.mjs +0 -974
  1319. package/dist/kinds-BQl5gN6e.mjs.map +0 -1
  1320. package/dist/lifecycle-DGQI7itS.mjs +0 -25
  1321. package/dist/lifecycle-DGQI7itS.mjs.map +0 -1
  1322. package/dist/load-DMxRl4VK.mjs +0 -28
  1323. package/dist/load-DMxRl4VK.mjs.map +0 -1
  1324. package/dist/loader-1DMiOq5s.mjs +0 -1303
  1325. package/dist/loader-1DMiOq5s.mjs.map +0 -1
  1326. package/dist/loops-Dn1i16FP.mjs +0 -205
  1327. package/dist/loops-Dn1i16FP.mjs.map +0 -1
  1328. package/dist/manifest-builder-CTw4FmZk.mjs +0 -189
  1329. package/dist/manifest-builder-CTw4FmZk.mjs.map +0 -1
  1330. package/dist/manifest-schema-gy37bD_D.mjs +0 -496
  1331. package/dist/manifest-schema-gy37bD_D.mjs.map +0 -1
  1332. package/dist/marketplace-tHWSsCUo.mjs +0 -257
  1333. package/dist/marketplace-tHWSsCUo.mjs.map +0 -1
  1334. package/dist/media-Bw-Q65ve.mjs +0 -294
  1335. package/dist/media-Bw-Q65ve.mjs.map +0 -1
  1336. package/dist/media-BzUDu7nl.mjs +0 -373
  1337. package/dist/media-BzUDu7nl.mjs.map +0 -1
  1338. package/dist/media-aahnKHze.mjs +0 -408
  1339. package/dist/media-aahnKHze.mjs.map +0 -1
  1340. package/dist/media-allowlist-RMrjo8TU.mjs +0 -45
  1341. package/dist/media-allowlist-RMrjo8TU.mjs.map +0 -1
  1342. package/dist/media-folders-kabR1oJf.mjs +0 -210
  1343. package/dist/media-folders-kabR1oJf.mjs.map +0 -1
  1344. package/dist/media-url-DloW2xrP.mjs +0 -43
  1345. package/dist/media-url-DloW2xrP.mjs.map +0 -1
  1346. package/dist/media-usage-CHHpV7ee.mjs +0 -282
  1347. package/dist/media-usage-CHHpV7ee.mjs.map +0 -1
  1348. package/dist/media-usage-activation-BinYqC_r.mjs +0 -78
  1349. package/dist/media-usage-activation-BinYqC_r.mjs.map +0 -1
  1350. package/dist/media-usage-work-C734aq6s.mjs +0 -1015
  1351. package/dist/media-usage-work-C734aq6s.mjs.map +0 -1
  1352. package/dist/media-usage-work-lxvV0-UO.mjs +0 -151
  1353. package/dist/media-usage-work-lxvV0-UO.mjs.map +0 -1
  1354. package/dist/menus-6RHNq4ZK.mjs +0 -753
  1355. package/dist/menus-6RHNq4ZK.mjs.map +0 -1
  1356. package/dist/menus-AuCTaO9W.mjs +0 -3704
  1357. package/dist/menus-AuCTaO9W.mjs.map +0 -1
  1358. package/dist/menus-BQKqf0Up.mjs +0 -92
  1359. package/dist/menus-BQKqf0Up.mjs.map +0 -1
  1360. package/dist/mode-nh0ppSX5.mjs +0 -23
  1361. package/dist/mode-nh0ppSX5.mjs.map +0 -1
  1362. package/dist/node-sqlite-compat-CDOEcc97.mjs +0 -63
  1363. package/dist/node-sqlite-compat-CDOEcc97.mjs.map +0 -1
  1364. package/dist/oauth-authorization-BtYQ80uU.mjs +0 -274
  1365. package/dist/oauth-authorization-BtYQ80uU.mjs.map +0 -1
  1366. package/dist/oauth-clients-C1S4NQHh.mjs +0 -266
  1367. package/dist/oauth-clients-C1S4NQHh.mjs.map +0 -1
  1368. package/dist/oauth-state-store-CpGi2FZH.mjs +0 -50
  1369. package/dist/oauth-state-store-CpGi2FZH.mjs.map +0 -1
  1370. package/dist/oauth-user-lookup-BNp1aFAr.mjs +0 -26
  1371. package/dist/oauth-user-lookup-BNp1aFAr.mjs.map +0 -1
  1372. package/dist/options-B41aLm1I.d.mts +0 -81
  1373. package/dist/options-B41aLm1I.d.mts.map +0 -1
  1374. package/dist/ownership-DoeAIuTg.mjs +0 -12
  1375. package/dist/ownership-DoeAIuTg.mjs.map +0 -1
  1376. package/dist/parse-CmFDTGzp.mjs +0 -81
  1377. package/dist/parse-CmFDTGzp.mjs.map +0 -1
  1378. package/dist/parse-QVS0t5oR.d.mts +0 -172
  1379. package/dist/parse-QVS0t5oR.d.mts.map +0 -1
  1380. package/dist/passkey-config-C-MxsV22.mjs +0 -46
  1381. package/dist/passkey-config-C-MxsV22.mjs.map +0 -1
  1382. package/dist/paths-kr8hAMMI.mjs +0 -77
  1383. package/dist/paths-kr8hAMMI.mjs.map +0 -1
  1384. package/dist/patterns-DyZJ67I7.mjs +0 -140
  1385. package/dist/patterns-DyZJ67I7.mjs.map +0 -1
  1386. package/dist/placeholder-Dn7MMdWr.d.mts +0 -306
  1387. package/dist/placeholder-Dn7MMdWr.d.mts.map +0 -1
  1388. package/dist/plan-CuQOeU9g.mjs +0 -135
  1389. package/dist/plan-CuQOeU9g.mjs.map +0 -1
  1390. package/dist/plugins-CGb-G98c.mjs +0 -261
  1391. package/dist/plugins-CGb-G98c.mjs.map +0 -1
  1392. package/dist/policy-C5wrDXYV.mjs +0 -45
  1393. package/dist/policy-C5wrDXYV.mjs.map +0 -1
  1394. package/dist/portable-text-BnRuiR1-.mjs +0 -307
  1395. package/dist/portable-text-BnRuiR1-.mjs.map +0 -1
  1396. package/dist/preview-xWNSR1Li.mjs +0 -107
  1397. package/dist/preview-xWNSR1Li.mjs.map +0 -1
  1398. package/dist/protocol-ClxN-m1e.d.mts +0 -61
  1399. package/dist/protocol-ClxN-m1e.d.mts.map +0 -1
  1400. package/dist/public-submission-CkmT2SKv.mjs +0 -250
  1401. package/dist/public-submission-CkmT2SKv.mjs.map +0 -1
  1402. package/dist/public-url-B5a0QAh6.mjs +0 -101
  1403. package/dist/public-url-B5a0QAh6.mjs.map +0 -1
  1404. package/dist/query-DHqFiqqH.mjs +0 -41
  1405. package/dist/query-DHqFiqqH.mjs.map +0 -1
  1406. package/dist/query-DT85BehD.mjs +0 -1125
  1407. package/dist/query-DT85BehD.mjs.map +0 -1
  1408. package/dist/rate-limit-iMvcu3eu.mjs +0 -120
  1409. package/dist/rate-limit-iMvcu3eu.mjs.map +0 -1
  1410. package/dist/receipt-Dpp1uaX9.mjs +0 -43
  1411. package/dist/receipt-Dpp1uaX9.mjs.map +0 -1
  1412. package/dist/redirect-BRoLiWQE.mjs +0 -19
  1413. package/dist/redirect-BRoLiWQE.mjs.map +0 -1
  1414. package/dist/redirect-xZNpBoGp.mjs +0 -417
  1415. package/dist/redirect-xZNpBoGp.mjs.map +0 -1
  1416. package/dist/redirects-D4EWeXAo.mjs +0 -148
  1417. package/dist/redirects-D4EWeXAo.mjs.map +0 -1
  1418. package/dist/redirects-DCKqwiWp.mjs +0 -446
  1419. package/dist/redirects-DCKqwiWp.mjs.map +0 -1
  1420. package/dist/registry-DaXmUsD4.mjs +0 -8369
  1421. package/dist/registry-DaXmUsD4.mjs.map +0 -1
  1422. package/dist/relations-JvBP8nGP.mjs +0 -776
  1423. package/dist/relations-JvBP8nGP.mjs.map +0 -1
  1424. package/dist/request-meta-B6blULFY.mjs +0 -141
  1425. package/dist/request-meta-B6blULFY.mjs.map +0 -1
  1426. package/dist/resolve-DAhHi5tn.mjs +0 -100
  1427. package/dist/resolve-DAhHi5tn.mjs.map +0 -1
  1428. package/dist/revision-BWEH2qQB.mjs +0 -368
  1429. package/dist/revision-BWEH2qQB.mjs.map +0 -1
  1430. package/dist/route-wire-DK600v4u.mjs +0 -197
  1431. package/dist/route-wire-DK600v4u.mjs.map +0 -1
  1432. package/dist/routes-BeZyaCzv.mjs +0 -272
  1433. package/dist/routes-BeZyaCzv.mjs.map +0 -1
  1434. package/dist/runner-CskI4Hmq.mjs +0 -6372
  1435. package/dist/runner-CskI4Hmq.mjs.map +0 -1
  1436. package/dist/runner-J3Cj6DGk.d.mts +0 -68
  1437. package/dist/runner-J3Cj6DGk.d.mts.map +0 -1
  1438. package/dist/schema-CK0ihqMR.mjs +0 -815
  1439. package/dist/schema-CK0ihqMR.mjs.map +0 -1
  1440. package/dist/schema-VtaZSrC-.mjs +0 -13
  1441. package/dist/schema-VtaZSrC-.mjs.map +0 -1
  1442. package/dist/scopes-BRnGlL9t.mjs +0 -34
  1443. package/dist/scopes-BRnGlL9t.mjs.map +0 -1
  1444. package/dist/search-D6-XK1Sn.mjs +0 -448
  1445. package/dist/search-D6-XK1Sn.mjs.map +0 -1
  1446. package/dist/sections-CBDCf7qR.mjs +0 -346
  1447. package/dist/sections-CBDCf7qR.mjs.map +0 -1
  1448. package/dist/seo-BdeEPDNW.mjs +0 -133
  1449. package/dist/seo-BdeEPDNW.mjs.map +0 -1
  1450. package/dist/seo-C61bnPuV.mjs +0 -114
  1451. package/dist/seo-C61bnPuV.mjs.map +0 -1
  1452. package/dist/service-BuRKLMxa.mjs +0 -150
  1453. package/dist/service-BuRKLMxa.mjs.map +0 -1
  1454. package/dist/session-user-CqkPtrPL.mjs +0 -61
  1455. package/dist/session-user-CqkPtrPL.mjs.map +0 -1
  1456. package/dist/settings-0u0lTthS.mjs +0 -51
  1457. package/dist/settings-0u0lTthS.mjs.map +0 -1
  1458. package/dist/settings-WHr45II1.mjs +0 -61
  1459. package/dist/settings-WHr45II1.mjs.map +0 -1
  1460. package/dist/settings-m5tIYnF0.mjs +0 -443
  1461. package/dist/settings-m5tIYnF0.mjs.map +0 -1
  1462. package/dist/setup-complete-B2_dhZZz.mjs +0 -65
  1463. package/dist/setup-complete-B2_dhZZz.mjs.map +0 -1
  1464. package/dist/setup-nonce-CJV9ftSK.mjs +0 -25
  1465. package/dist/setup-nonce-CJV9ftSK.mjs.map +0 -1
  1466. package/dist/site-settings-KJOqJW6W.mjs +0 -50
  1467. package/dist/site-settings-KJOqJW6W.mjs.map +0 -1
  1468. package/dist/site-url-Ceq_AUK0.mjs +0 -16
  1469. package/dist/site-url-Ceq_AUK0.mjs.map +0 -1
  1470. package/dist/slugify-T5GHH42D.mjs +0 -17
  1471. package/dist/slugify-T5GHH42D.mjs.map +0 -1
  1472. package/dist/snapshot-Dzsn3NZa.mjs +0 -414
  1473. package/dist/snapshot-Dzsn3NZa.mjs.map +0 -1
  1474. package/dist/ssrf-DsmvZB80.mjs +0 -406
  1475. package/dist/ssrf-DsmvZB80.mjs.map +0 -1
  1476. package/dist/stage-Csnh_yEd.mjs +0 -662
  1477. package/dist/stage-Csnh_yEd.mjs.map +0 -1
  1478. package/dist/staged-Q7pnEVvI.mjs +0 -686
  1479. package/dist/staged-Q7pnEVvI.mjs.map +0 -1
  1480. package/dist/state-kGdOMQRY.mjs +0 -153
  1481. package/dist/state-kGdOMQRY.mjs.map +0 -1
  1482. package/dist/status-D39Fpy8R.mjs +0 -30
  1483. package/dist/status-D39Fpy8R.mjs.map +0 -1
  1484. package/dist/storage-query-C7iy6eUb.d.mts +0 -1615
  1485. package/dist/storage-query-C7iy6eUb.d.mts.map +0 -1
  1486. package/dist/tar-BWiraSUY.mjs +0 -449
  1487. package/dist/tar-BWiraSUY.mjs.map +0 -1
  1488. package/dist/target-DOFs5oUC.mjs +0 -27
  1489. package/dist/target-DOFs5oUC.mjs.map +0 -1
  1490. package/dist/taxonomies-BS40Wxvi.mjs +0 -939
  1491. package/dist/taxonomies-BS40Wxvi.mjs.map +0 -1
  1492. package/dist/taxonomies-DAZU9VDd.mjs +0 -697
  1493. package/dist/taxonomies-DAZU9VDd.mjs.map +0 -1
  1494. package/dist/taxonomy-XzLE95Vx.mjs +0 -525
  1495. package/dist/taxonomy-XzLE95Vx.mjs.map +0 -1
  1496. package/dist/tokens-DSQan4p7.mjs +0 -171
  1497. package/dist/tokens-DSQan4p7.mjs.map +0 -1
  1498. package/dist/transfer-BcCVxDgf.mjs +0 -166
  1499. package/dist/transfer-BcCVxDgf.mjs.map +0 -1
  1500. package/dist/transfer-OMrancOt.mjs +0 -8873
  1501. package/dist/transfer-OMrancOt.mjs.map +0 -1
  1502. package/dist/transfer-download-Dc-ltjZY.mjs +0 -33
  1503. package/dist/transfer-download-Dc-ltjZY.mjs.map +0 -1
  1504. package/dist/transformations-Ddx7UtrY.mjs +0 -196
  1505. package/dist/transformations-Ddx7UtrY.mjs.map +0 -1
  1506. package/dist/transport-flR-Zmds.d.mts +0 -42
  1507. package/dist/transport-flR-Zmds.d.mts.map +0 -1
  1508. package/dist/transport-umzEbgYO.mjs +0 -136
  1509. package/dist/transport-umzEbgYO.mjs.map +0 -1
  1510. package/dist/trusted-proxy-BqfQFs6a.mjs +0 -51
  1511. package/dist/trusted-proxy-BqfQFs6a.mjs.map +0 -1
  1512. package/dist/turnstile-Do60baId.mjs +0 -58
  1513. package/dist/turnstile-Do60baId.mjs.map +0 -1
  1514. package/dist/types-BT1p3uyb.d.mts +0 -1735
  1515. package/dist/types-BT1p3uyb.d.mts.map +0 -1
  1516. package/dist/types-BYaPWs_F.d.mts +0 -198
  1517. package/dist/types-BYaPWs_F.d.mts.map +0 -1
  1518. package/dist/types-C-Xp0CcN.mjs +0 -103
  1519. package/dist/types-C-Xp0CcN.mjs.map +0 -1
  1520. package/dist/types-CLTQ9b_r.d.mts +0 -374
  1521. package/dist/types-CLTQ9b_r.d.mts.map +0 -1
  1522. package/dist/types-CPkQw2T2.d.mts +0 -85
  1523. package/dist/types-CPkQw2T2.d.mts.map +0 -1
  1524. package/dist/types-CQLvuXoZ.d.mts +0 -198
  1525. package/dist/types-CQLvuXoZ.d.mts.map +0 -1
  1526. package/dist/types-CV5xszpF.d.mts +0 -363
  1527. package/dist/types-CV5xszpF.d.mts.map +0 -1
  1528. package/dist/types-CdEMurNh.mjs +0 -77
  1529. package/dist/types-CdEMurNh.mjs.map +0 -1
  1530. package/dist/types-CxwtnuSS.mjs +0 -37
  1531. package/dist/types-CxwtnuSS.mjs.map +0 -1
  1532. package/dist/types-DGw8KMX_.mjs +0 -16
  1533. package/dist/types-DGw8KMX_.mjs.map +0 -1
  1534. package/dist/types-DOfVMPoo.d.mts +0 -125
  1535. package/dist/types-DOfVMPoo.d.mts.map +0 -1
  1536. package/dist/types-LtS0SM4F.d.mts +0 -287
  1537. package/dist/types-LtS0SM4F.d.mts.map +0 -1
  1538. package/dist/types-ifgOkcBa.d.mts +0 -916
  1539. package/dist/types-ifgOkcBa.d.mts.map +0 -1
  1540. package/dist/update-check-DOc2aBIU.mjs +0 -161
  1541. package/dist/update-check-DOc2aBIU.mjs.map +0 -1
  1542. package/dist/upload-attempts-nzdc3cay.mjs +0 -32
  1543. package/dist/upload-attempts-nzdc3cay.mjs.map +0 -1
  1544. package/dist/url-pattern-hVbqpXs6.mjs +0 -41
  1545. package/dist/url-pattern-hVbqpXs6.mjs.map +0 -1
  1546. package/dist/user-Dnp53zTq.mjs +0 -171
  1547. package/dist/user-Dnp53zTq.mjs.map +0 -1
  1548. package/dist/utils-CoV-PYhg.mjs +0 -439
  1549. package/dist/utils-CoV-PYhg.mjs.map +0 -1
  1550. package/dist/validate-CfQ9_YlD.d.mts +0 -468
  1551. package/dist/validate-CfQ9_YlD.d.mts.map +0 -1
  1552. package/dist/validate-DBX3DKtL.mjs +0 -516
  1553. package/dist/validate-DBX3DKtL.mjs.map +0 -1
  1554. package/dist/validation-BJMiDhB1.mjs +0 -219
  1555. package/dist/validation-BJMiDhB1.mjs.map +0 -1
  1556. package/dist/version-BaT8yKIT.mjs +0 -7
  1557. package/dist/version-BaT8yKIT.mjs.map +0 -1
  1558. package/dist/version-BwEGkqkx.mjs +0 -9
  1559. package/dist/version-BwEGkqkx.mjs.map +0 -1
  1560. package/dist/virtual-modules-B6vJW15k.mjs +0 -516
  1561. package/dist/virtual-modules-B6vJW15k.mjs.map +0 -1
  1562. package/dist/widgets-BkxBAgZ_.mjs +0 -126
  1563. package/dist/widgets-BkxBAgZ_.mjs.map +0 -1
  1564. package/dist/wxr-taxonomies-D7gAJyOF.mjs +0 -453
  1565. package/dist/wxr-taxonomies-D7gAJyOF.mjs.map +0 -1
  1566. package/dist/wxr-taxonomies-DM5J9AsT.d.mts +0 -34
  1567. package/dist/wxr-taxonomies-DM5J9AsT.d.mts.map +0 -1
  1568. package/dist/zod-generator-BQilAboC.mjs +0 -424
  1569. package/dist/zod-generator-BQilAboC.mjs.map +0 -1
  1570. /package/dist/{api-tokens-BLz2MtQs.mjs → api-tokens-CdP49zVF.mjs} +0 -0
  1571. /package/dist/{ssrf-tA45vIPX.mjs → ssrf-DM0E-D_8.mjs} +0 -0
@@ -0,0 +1,4379 @@
1
+ import { i as MigrationLockHeldError, n as MIGRATION_RACE_WAIT_MS, r as MigrationFailedError } from "./runner-C5NeAkBQ.mjs";
2
+ import { n as isMissingTableError } from "./db-errors-CBD5yMro.mjs";
3
+ import { d as isSqlite, m as listTablesLike } from "./dialect-helpers-7_IqPdh9.mjs";
4
+ import { r as getI18nConfig, s as resolveContentCreateLocale } from "./config-CvmjJcVw.mjs";
5
+ import { C as definePlugin, S as resolveExclusiveHooks, _ as isContentSaveRejection, b as createHookPipeline, c as inspectSandboxHookResult, g as ContentSaveRejectedError, h as EmailPipeline, v as EXCLUSIVE_HOOK_KEY_PREFIX, x as getActiveContentSaveHookName } from "./menus-Cl4kwdt7.mjs";
6
+ import { t as after } from "./after-PcEvMY_6.mjs";
7
+ import { getRequestContext } from "./request-context.mjs";
8
+ import { t as OptionsRepository } from "./options-DhrI7eMv.mjs";
9
+ import { o as validateEncryptionKeyAtStartup } from "./secrets-Cc_O3Kp-.mjs";
10
+ import { n as decodeBase64Bytes } from "./base64-BQBZwXCx.mjs";
11
+ import { n as initWithLock, t as createInitLock } from "./init-lock-BGmew3r1.mjs";
12
+ import { r as singleFlightCached, t as createSingleFlightCache } from "./single-flight-cache-D0EaGbN1.mjs";
13
+ import { a as staleStoredKeys, i as keepKnownFields, t as ContentRepository } from "./content-DQSRZTVw.mjs";
14
+ import { m as isStoragelessFieldRow, p as isStoragelessField } from "./types-BGmQ0uXs.mjs";
15
+ import { r as requestCached } from "./request-cache-C4CIo5ad.mjs";
16
+ import { n as ContentMutationConflictError } from "./types-D2NEXtgB.mjs";
17
+ import { t as RevisionRepository } from "./revision-BVp8S0e_.mjs";
18
+ import { t as MediaRepository } from "./media-Bws9f18T.mjs";
19
+ import { t as CommentRepository } from "./comment-UvIWDKcW.mjs";
20
+ import { t as AuditRepository } from "./audit-_gieLVGU.mjs";
21
+ import { b as updatePluginMediaMetadata, d as createTaxonomyAccessWithWrite, l as createSiteInfo, r as createCommentAccess } from "./context-BYptmCN0.mjs";
22
+ import { t as RedirectRepository } from "./redirect-DiS_3mA4.mjs";
23
+ import { a as mergeStagedReferenceBaselines, i as STAGED_REFERENCES_KEY, o as mergeStagedReferences, r as STAGED_REFERENCES_BASELINE_KEY } from "./staged-CD_imitH.mjs";
24
+ import { n as normalizeMediaValue } from "./normalize-BuWa0lvd.mjs";
25
+ import { i as resolveUploadMimeType, n as normalizeMime, t as matchesMimeAllowlist } from "./mime-B5M9R53H.mjs";
26
+ import { t as enrichImageMetadata } from "./enrich-Duu4sV3a.mjs";
27
+ import { i as scheduledPolicyRejectionKey } from "./content-policy-Dm32n9C6.mjs";
28
+ import { t as FTSManager } from "./fts-manager-Beg3e4bI.mjs";
29
+ import { c as getDb, g as selectTaxonomyDefs } from "./loader-CswroUbe.mjs";
30
+ import { kyselyLogOption } from "./database/instrumentation.mjs";
31
+ import { r as primeRegisteredCollections } from "./collection-slugs-cache-e09QeJQt.mjs";
32
+ import { A as handleContentDelete, B as handleContentSchedule, D as handleContentCountScheduled, E as handleContentCompare, F as handleContentList, G as handleScheduledPolicyRejection, H as handleContentUnpublish, I as handleContentListTrashed, J as validateRev, K as storagelessDataKeyError, L as handleContentPermanentDelete, M as handleContentDuplicate, N as handleContentGet, O as handleContentCountTrashed, P as handleContentGetIncludingTrashed, R as handleContentPublish, S as buildManifestCollections, T as handleContentAuthors, U as handleContentUnschedule, V as handleContentTranslations, W as handleContentUpdate, b as handleRevisionList, h as syncDeclaredStorageIndexes, j as handleContentDiscardDraft, k as handleContentCreate, p as loadBundleFromR2, q as liveReferenceSelection, w as changedStoragelessDataKeys, x as handleRevisionRestore, y as handleRevisionGet, z as handleContentRestore } from "./api-D-7BNHoz.mjs";
33
+ import { r as activateMediaUsageCapture } from "./activation-Dim4YMM7.mjs";
34
+ import { D as deleteContentMediaUsage, H as markContentMediaUsageCollectionStale, O as findNonTranslatableSiblingContentIds, T as processMediaUsageWorkAfterWrite, W as MediaUsageRepository, k as refreshContentMediaUsageAfterWrite, n as SchemaRegistry, t as SchemaError } from "./registry-TUVaid9Z.mjs";
35
+ import { n as computeContentHash, r as hashString } from "./hash-BJaeek87.mjs";
36
+ import { a as resolveBlockTypes, i as normalizeBlocksData, r as inferBlockTypes } from "./block-values-gCX4HBSl.mjs";
37
+ import { f as resolveReferenceSelection } from "./relations-CA_QuMTU.mjs";
38
+ import { n as recordSchedulerHeartbeatSafely } from "./dashboard-D0jcgBD7.mjs";
39
+ import { r as getRegistryConfigInput, s as resolveManifestRegistryConfig } from "./config-DBU6iUQB.mjs";
40
+ import { n as VERSION, t as COMMIT } from "./version-DHmGpcch.mjs";
41
+ import { t as ErrorCode } from "./errors-BOYNdIo3.mjs";
42
+ import { t as removeUploadAttempt } from "./upload-attempts-DPFC4COv.mjs";
43
+ import { a as handleMediaRegisterUpload, i as handleMediaList, n as handleMediaDelete, o as handleMediaReplaceMetadata, r as handleMediaGet, s as handleMediaUpdate, t as handleMediaCreate } from "./media--dJRU6i2.mjs";
44
+ import { n as rowToPluginState, t as PluginStateRepository } from "./state-CFbGZziM.mjs";
45
+ import { c as invalidateSiteSettingsCache } from "./settings-CWb84sDm.mjs";
46
+ import { isRecord } from "./plugin-utils.mjs";
47
+ import { n as assertSiteWriteAllowed } from "./fence-C3MUitUN.mjs";
48
+ import { i as normalizeManifestRoute$1, r as normalizeManifestHook, t as HOOK_NAMES } from "./manifest-schema-DNb4XmTS.mjs";
49
+ import { i as getSandboxRouteErrorDetails } from "./types-qHVygneX.mjs";
50
+ import { a as normalizePluginCapabilities, o as warnDeprecatedPluginCapabilities } from "./types-sUHaH7lC.mjs";
51
+ import { n as DEFAULT_MAX_UPLOAD_SIZE, r as formatFileSize, t as CONTENT_TYPE_RE } from "./media-1WC65OCB.mjs";
52
+ import { t as safeJsonSchemaToZod } from "./json-schema-v_D_pPNG.mjs";
53
+ import { t as getTrustedProxyHeaders } from "./trusted-proxy-BU3Nx9g1.mjs";
54
+ import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "./request-meta-bwZ_NsvU.mjs";
55
+ import { adaptSandboxEntry } from "./plugins/adapt-sandbox-entry.mjs";
56
+ import { t as GLOBAL_UPLOAD_ALLOWLIST } from "./media-allowlist-BJB7MFx6.mjs";
57
+ import { n as CronExecutor, r as setCronTasksEnabled } from "./cron-D7obTIfk.mjs";
58
+ import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from "./email-console-FFVJWb9N.mjs";
59
+ import { t as PluginRouteRequestError } from "./route-wire-Ca_HWCZ5.mjs";
60
+ import { a as toRouteCallerInfo, n as buildRouteMeta, r as parseRouteInput, t as PluginRouteRegistry } from "./routes-BerZFrvV.mjs";
61
+ import { n as enforceRuntimeMigrationPolicy } from "./policy-DLQ--Hnm.mjs";
62
+ import { n as getSiteBaseUrl, r as resolveSiteOrigin } from "./site-url-MB685I5X.mjs";
63
+ import { t as getAuthMode } from "./mode-C-D3qICy.mjs";
64
+ import { n as lookupContentAuthor, r as sendCommentNotification, t as submitPublicComment } from "./public-submission-Di3TsgzX.mjs";
65
+ import { t as pluginToolName } from "./plugin-tool-name-DDUgN_0p.mjs";
66
+ import { t as AUTO_SEED_COMPLETE_OPTION } from "./ownership-MiyA7qKO.mjs";
67
+ import { u as maybeRunScheduledBackup } from "./backup-3FgVxiDJ.mjs";
68
+ import { t as cleanupExpiredChallenges } from "./challenge-store-C17BYx2-.mjs";
69
+ import { n as createComment, r as moderateComment, t as CommentStatusConflictError } from "./service-Bo_usgSk.mjs";
70
+ import { n as enableRuntimePlugin, t as disableRuntimePlugin } from "./lifecycle-7vUJeFRe.mjs";
71
+ import { Kysely, sql } from "kysely";
72
+ import { z } from "zod";
73
+ import { ulid } from "ulidx";
74
+ import * as path from "node:path";
75
+ import { Permissions } from "@emdash-cms/auth";
76
+ import { isJsonPostRouteContract } from "@emdash-cms/plugin-types";
77
+ import virtualConfig from "virtual:emdash/config";
78
+ import { getLocaleDir, resolveLocale } from "@emdash-cms/admin/locales";
79
+ import { normalizePluginPagePath, validateBlockResponse, validateContentEditorActionResponse } from "@emdash-cms/blocks/server";
80
+ import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
81
+
82
+ //#region src/api/handlers/media-upload.ts
83
+ /**
84
+ * Programmatic media upload handler (MCP `media_upload` tool).
85
+ *
86
+ * Accepts file bytes as base64, then runs the same pipeline as the multipart
87
+ * REST upload route: allowlist + size validation, content-hash deduplication,
88
+ * storage upload, image metadata enrichment, and record creation.
89
+ */
90
+ function fail(code, message) {
91
+ return {
92
+ success: false,
93
+ error: {
94
+ code,
95
+ message
96
+ }
97
+ };
98
+ }
99
+ /** Same relative-URL shape the REST media routes return. */
100
+ function withUrl(item) {
101
+ return {
102
+ ...item,
103
+ url: `/_emdash/api/media/file/${item.storageKey}`
104
+ };
105
+ }
106
+ /** Decode the file bytes after rejecting an oversized encoded payload. */
107
+ async function acquireBytes(input, maxUploadSize) {
108
+ if (input.base64.length * 3 / 4 > maxUploadSize) return fail("PAYLOAD_TOO_LARGE", `File exceeds maximum size of ${formatFileSize(maxUploadSize)}`);
109
+ try {
110
+ return {
111
+ bytes: decodeBase64Bytes(input.base64),
112
+ mimeType: input.contentType
113
+ };
114
+ } catch {
115
+ return fail("VALIDATION_ERROR", "Invalid base64 data");
116
+ }
117
+ }
118
+ /**
119
+ * Upload a media file from base64 data.
120
+ *
121
+ * Mirrors the REST `POST /_emdash/api/media` route: global MIME allowlist,
122
+ * size limit, content-hash dedupe (returns the existing item with
123
+ * `deduplicated: true`), storage upload with cleanup on failure, and
124
+ * image metadata enrichment (dimensions, blurhash, dominant color).
125
+ */
126
+ async function handleMediaUpload(db, storage, input, hooks = {}) {
127
+ if (!input.base64 || !input.contentType) return fail("VALIDATION_ERROR", "base64 and contentType are required");
128
+ const rawMax = input.maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE;
129
+ if (!Number.isFinite(rawMax) || rawMax <= 0) return fail("CONFIGURATION_ERROR", "Invalid maxUploadSize configuration");
130
+ const acquired = await acquireBytes(input, rawMax);
131
+ if ("success" in acquired) return acquired;
132
+ const { bytes } = acquired;
133
+ let filename = input.filename;
134
+ let mimeType = resolveUploadMimeType(filename, acquired.mimeType);
135
+ let size = bytes.byteLength;
136
+ if (!CONTENT_TYPE_RE.test(mimeType)) return fail("VALIDATION_ERROR", "Invalid content type");
137
+ if (!matchesMimeAllowlist(mimeType, GLOBAL_UPLOAD_ALLOWLIST)) return fail("INVALID_TYPE", "File type not allowed");
138
+ if (bytes.byteLength > rawMax) return fail("PAYLOAD_TOO_LARGE", `File exceeds maximum size of ${formatFileSize(rawMax)}`);
139
+ try {
140
+ if (hooks.beforeUpload) {
141
+ const processed = await hooks.beforeUpload({
142
+ name: filename,
143
+ type: mimeType,
144
+ size: bytes.byteLength
145
+ });
146
+ filename = processed.name;
147
+ mimeType = normalizeMime(processed.type);
148
+ size = processed.size;
149
+ if (!CONTENT_TYPE_RE.test(processed.type)) return fail("VALIDATION_ERROR", "Invalid content type");
150
+ if (!matchesMimeAllowlist(mimeType, GLOBAL_UPLOAD_ALLOWLIST)) return fail("INVALID_TYPE", "File type not allowed");
151
+ }
152
+ const contentHash = await computeContentHash(bytes);
153
+ const repo = new MediaRepository(db);
154
+ const existing = await repo.findByContentHash(contentHash);
155
+ if (existing) return {
156
+ success: true,
157
+ data: {
158
+ item: withUrl(existing),
159
+ deduplicated: true
160
+ }
161
+ };
162
+ const storageKey = `${ulid()}${path.extname(filename)}`;
163
+ await storage.upload({
164
+ key: storageKey,
165
+ body: bytes,
166
+ contentType: mimeType
167
+ });
168
+ try {
169
+ const enriched = await enrichImageMetadata(bytes, mimeType);
170
+ return {
171
+ success: true,
172
+ data: { item: withUrl(await repo.create({
173
+ filename,
174
+ mimeType,
175
+ size,
176
+ width: enriched.width,
177
+ height: enriched.height,
178
+ alt: input.alt,
179
+ caption: input.caption,
180
+ storageKey,
181
+ contentHash,
182
+ blurhash: enriched.blurhash,
183
+ dominantColor: enriched.dominantColor,
184
+ authorId: input.authorId
185
+ })) }
186
+ };
187
+ } catch (error) {
188
+ try {
189
+ await storage.delete(storageKey);
190
+ } catch {}
191
+ throw error;
192
+ }
193
+ } catch {
194
+ return fail("UPLOAD_ERROR", "Upload failed");
195
+ }
196
+ }
197
+
198
+ //#endregion
199
+ //#region src/i18n/repair-locale-casing.ts
200
+ /** Rewrite stored locales to the exact casing used by the site configuration. */
201
+ async function repairLocaleCasing(db, configuredLocales) {
202
+ const tableNames = await listTablesLike(db, "ec_%");
203
+ for (const tableName of tableNames) {
204
+ const table = sql.ref(tableName);
205
+ for (const locale of configuredLocales) await sql`
206
+ UPDATE ${table} AS target
207
+ SET locale = ${locale}
208
+ WHERE lower(target.locale) = lower(${locale})
209
+ AND target.locale != ${locale}
210
+ AND NOT EXISTS (
211
+ SELECT 1
212
+ FROM ${table} AS existing
213
+ WHERE existing.slug = target.slug AND existing.locale = ${locale}
214
+ )
215
+ AND target.id = (
216
+ SELECT MIN(candidate.id)
217
+ FROM ${table} AS candidate
218
+ WHERE candidate.slug = target.slug
219
+ AND lower(candidate.locale) = lower(${locale})
220
+ AND candidate.locale != ${locale}
221
+ )
222
+ `.execute(db);
223
+ }
224
+ }
225
+
226
+ //#endregion
227
+ //#region src/i18n/taxonomy-locale-diagnostic.ts
228
+ const REPAIR_GUIDE = "https://docs.emdashcms.com/guides/internationalization/#repairing-taxonomy-locale-mismatches";
229
+ async function warnAboutUnconfiguredTaxonomyLocales(db, configuredLocales, definitionLocales) {
230
+ const supportedLocales = configuredLocales.length > 0 ? configuredLocales : ["en"];
231
+ const definitionRows = definitionLocales === void 0 ? await db.selectFrom("_emdash_taxonomy_defs").select("locale").distinct().where("locale", "not in", supportedLocales).execute() : [...new Set(definitionLocales)].filter((locale) => !supportedLocales.includes(locale)).map((locale) => ({ locale }));
232
+ const termRows = await db.selectFrom("taxonomies").select("locale").distinct().where("locale", "not in", supportedLocales).execute();
233
+ const mismatches = [...definitionRows.map(({ locale }) => ({
234
+ source: "definitions",
235
+ locale
236
+ })), ...termRows.map(({ locale }) => ({
237
+ source: "terms",
238
+ locale
239
+ }))].toSorted((a, b) => a.source.localeCompare(b.source) || a.locale.localeCompare(b.locale));
240
+ if (mismatches.length === 0) return;
241
+ const details = mismatches.map(({ source, locale }) => `${source}: ${locale}`).join("; ");
242
+ console.warn(`EmDash: Taxonomy rows use locales outside the configured locales (${supportedLocales.join(", ")}): ${details}. Locale-scoped reads may not return these rows. Review and repair them explicitly: ${REPAIR_GUIDE}`);
243
+ }
244
+
245
+ //#endregion
246
+ //#region src/plugins/sandbox/proxy.ts
247
+ const SANDBOX_REJECTION = Symbol("emdash:sandbox-save-rejection");
248
+ const VALID_METADATA_KINDS = new Set([
249
+ "meta",
250
+ "property",
251
+ "link",
252
+ "jsonld"
253
+ ]);
254
+ const VALID_LINK_REL = new Set([
255
+ "canonical",
256
+ "alternate",
257
+ "author",
258
+ "license",
259
+ "nlweb",
260
+ "site.standard.document"
261
+ ]);
262
+ function isValidMetadataContribution(value) {
263
+ if (!value || typeof value !== "object" || !("kind" in value)) return false;
264
+ const contribution = value;
265
+ if (typeof contribution.kind !== "string" || !VALID_METADATA_KINDS.has(contribution.kind)) return false;
266
+ if (contribution.kind === "meta") return typeof contribution.name === "string" && typeof contribution.content === "string";
267
+ if (contribution.kind === "property") return typeof contribution.property === "string" && typeof contribution.content === "string";
268
+ if (contribution.kind === "link") return typeof contribution.href === "string" && typeof contribution.rel === "string" && VALID_LINK_REL.has(contribution.rel);
269
+ return contribution.graph != null && typeof contribution.graph === "object";
270
+ }
271
+ function getSandboxSaveRejectionDetails(error) {
272
+ if (!(error instanceof ContentSaveRejectedError) || !(SANDBOX_REJECTION in error)) return null;
273
+ const details = Object.getOwnPropertyDescriptor(error, SANDBOX_REJECTION)?.value;
274
+ if (!details || typeof details !== "object") return null;
275
+ if (!("pluginId" in details) || typeof details.pluginId !== "string") return null;
276
+ if (!("reason" in details) || typeof details.reason !== "string") return null;
277
+ return {
278
+ pluginId: details.pluginId,
279
+ reason: details.reason
280
+ };
281
+ }
282
+ /**
283
+ * Represent an isolate-backed plugin inside the normal host hook pipeline.
284
+ * The proxy contains metadata and RPC handlers only; plugin code remains in
285
+ * the runner isolate.
286
+ */
287
+ function createSandboxedPluginProxy(manifest, instance) {
288
+ const hooks = {};
289
+ for (const entry of manifest.hooks) {
290
+ const hook = normalizeManifestHook(entry);
291
+ if (hook.name === "page:fragments") continue;
292
+ hooks[hook.name] = {
293
+ handler: async (event) => {
294
+ const result = await instance.invokeHook(hook.name, event);
295
+ if (hook.name === "page:metadata") {
296
+ if (result == null) return null;
297
+ const contributions = (Array.isArray(result) ? result : [result]).filter(isValidMetadataContribution);
298
+ return contributions.length > 0 ? contributions : null;
299
+ }
300
+ if (hook.name !== "content:beforeSave") return result;
301
+ const inspection = inspectSandboxHookResult(result);
302
+ if (inspection.kind === "error") {
303
+ const error = new ContentSaveRejectedError("Save rejected by a sandboxed plugin");
304
+ return Promise.reject(Object.assign(error, { [SANDBOX_REJECTION]: {
305
+ pluginId: manifest.id,
306
+ reason: inspection.error.reason
307
+ } }));
308
+ }
309
+ if (inspection.kind === "malformed") throw new Error(`Sandboxed plugin "${manifest.id}" returned an invalid hook result`);
310
+ return result;
311
+ },
312
+ priority: hook.priority,
313
+ timeout: hook.timeout,
314
+ dependencies: hook.dependencies,
315
+ errorPolicy: hook.errorPolicy,
316
+ exclusive: hook.exclusive
317
+ };
318
+ }
319
+ return adaptSandboxEntry({ hooks }, {
320
+ id: manifest.id,
321
+ version: manifest.version,
322
+ entrypoint: "",
323
+ format: "standard",
324
+ capabilities: manifest.capabilities,
325
+ allowedHosts: manifest.allowedHosts,
326
+ storage: manifest.storage,
327
+ adminPages: manifest.admin.pages,
328
+ adminWidgets: manifest.admin.widgets,
329
+ editorPanels: manifest.admin.editorPanels,
330
+ editorActions: manifest.admin.editorActions,
331
+ settingsSchema: manifest.admin.settingsSchema,
332
+ portableTextBlocks: manifest.admin.portableTextBlocks,
333
+ fieldWidgets: manifest.admin.fieldWidgets,
334
+ mcp: manifest.mcp
335
+ });
336
+ }
337
+
338
+ //#endregion
339
+ //#region src/plugins/sandbox/runner-options.ts
340
+ /**
341
+ * Build platform sandbox options with the same normalized site context used
342
+ * by trusted plugin hooks and routes.
343
+ */
344
+ function createSandboxRunnerOptions(options, siteInfo) {
345
+ return {
346
+ ...options,
347
+ siteInfo: createSiteInfo(siteInfo ?? {})
348
+ };
349
+ }
350
+
351
+ //#endregion
352
+ //#region src/media/usage/cleanup.ts
353
+ const MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT = 250;
354
+ const MEDIA_USAGE_CLEANUP_DELETE_LIMIT = 50;
355
+ const MEDIA_USAGE_CLEANUP_WRITE_LEASE_DELETE_LIMIT = 49;
356
+ const MEDIA_USAGE_CLEANUP_INTERVAL_MS = 60 * 1e3;
357
+ const MEDIA_USAGE_CLEANUP_LEASE_MS = 300 * 1e3;
358
+ const MEDIA_USAGE_CLEANUP_SAFETY_WINDOW_MS = 3600 * 1e3;
359
+ const MEDIA_USAGE_CLEANUP_TIME_BUDGET_MS = 5 * 1e3;
360
+ /**
361
+ * Reclaims a bounded window of obsolete media-usage occurrences.
362
+ *
363
+ * The persisted claim makes a cron tick single-flight across Worker isolates
364
+ * and Node processes.
365
+ */
366
+ async function cleanupMediaUsage(db) {
367
+ const startedMs = Date.now();
368
+ const canIssueStatement = () => withinBudget(startedMs);
369
+ const repo = new MediaUsageRepository(db);
370
+ const leaseToken = ulid();
371
+ const claim = await repo.claimMediaUsageCleanup({
372
+ leaseToken,
373
+ leaseDurationSeconds: MEDIA_USAGE_CLEANUP_LEASE_MS / 1e3,
374
+ nextEligibleDelaySeconds: MEDIA_USAGE_CLEANUP_INTERVAL_MS / 1e3,
375
+ sweepSafetyWindowSeconds: MEDIA_USAGE_CLEANUP_SAFETY_WINDOW_MS / 1e3
376
+ });
377
+ if (!claim) return emptyResult("skipped", elapsedSince(startedMs));
378
+ let candidateRows = 0;
379
+ let deletedOrphans = 0;
380
+ let deletedStale = 0;
381
+ let deletedAbandoned = 0;
382
+ let deletedWriteLeases = 0;
383
+ let backlogLowerBound = 0;
384
+ let scanHasMore = false;
385
+ let nextCursor = claim.cursor;
386
+ let sweepComplete = false;
387
+ try {
388
+ if (canIssueStatement()) deletedWriteLeases = await repo.deleteExpiredGenerationWriteLeases(MEDIA_USAGE_CLEANUP_WRITE_LEASE_DELETE_LIMIT, cleanupLease(leaseToken), canIssueStatement);
389
+ const cutoff = claim.scanBeforeAt;
390
+ const candidates = await repo.findMediaUsageCleanupCandidates({
391
+ cutoff,
392
+ cursor: claim.cursor,
393
+ limit: MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT,
394
+ cleanupLease: cleanupLease(leaseToken),
395
+ canIssueStatement
396
+ });
397
+ if (candidates) {
398
+ candidateRows = candidates.length;
399
+ scanHasMore = candidates.length === MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT;
400
+ const selected = selectCleanupCandidates(candidates, claim.claimedAt);
401
+ backlogLowerBound = selected.orphanIds.length + selected.staleIds.length + selected.abandonedIds.length;
402
+ const completedTargets = /* @__PURE__ */ new Set();
403
+ let canContinue = true;
404
+ if (canIssueStatement() && selected.orphanIds.length > 0) {
405
+ deletedOrphans = await repo.deleteOrphanOccurrencesOlderThan(cutoff, selected.orphanIds.length, {
406
+ candidateIds: selected.orphanIds,
407
+ cleanupLease: cleanupLease(leaseToken),
408
+ canIssueStatement
409
+ });
410
+ canContinue = deletedOrphans === selected.orphanIds.length;
411
+ if (canContinue) completedTargets.add("orphan");
412
+ }
413
+ if (canContinue && canIssueStatement() && selected.staleIds.length > 0) {
414
+ deletedStale = await repo.deleteStaleGenerationsOlderThan(cutoff, selected.staleIds.length, {
415
+ candidateIds: selected.staleIds,
416
+ cleanupLease: cleanupLease(leaseToken),
417
+ canIssueStatement
418
+ });
419
+ canContinue = deletedStale === selected.staleIds.length;
420
+ if (canContinue) completedTargets.add("stale");
421
+ }
422
+ if (canContinue && canIssueStatement() && selected.abandonedIds.length > 0) {
423
+ deletedAbandoned = await repo.deleteAbandonedGenerationsOlderThan(cutoff, selected.abandonedIds.length, {
424
+ candidateIds: selected.abandonedIds,
425
+ cleanupLease: cleanupLease(leaseToken),
426
+ canIssueStatement
427
+ });
428
+ if (deletedAbandoned === selected.abandonedIds.length) completedTargets.add("abandoned");
429
+ }
430
+ const hasIncompleteTargets = selected.entries.some((entry) => entry.target !== null && !completedTargets.has(entry.target));
431
+ sweepComplete = !scanHasMore && selected.entries.length === candidates.length && !hasIncompleteTargets;
432
+ nextCursor = sweepComplete ? null : cursorAfterCompletedCandidates(selected, completedTargets, claim.cursor);
433
+ }
434
+ const durationMs = elapsedSince(startedMs);
435
+ return {
436
+ status: await repo.completeMediaUsageCleanup({
437
+ leaseToken,
438
+ nextCursor,
439
+ sweepComplete,
440
+ candidateCount: candidateRows,
441
+ deletedOrphans,
442
+ deletedStale,
443
+ deletedAbandoned,
444
+ deletedWriteLeases,
445
+ backlogLowerBound,
446
+ scanHasMore,
447
+ durationMs
448
+ }) ? "completed" : "skipped",
449
+ candidateRows,
450
+ deletedRows: deletedOrphans + deletedStale + deletedAbandoned,
451
+ deletedOrphans,
452
+ deletedStale,
453
+ deletedAbandoned,
454
+ deletedWriteLeases,
455
+ backlogLowerBound,
456
+ scanHasMore,
457
+ durationMs
458
+ };
459
+ } catch (error) {
460
+ const durationMs = elapsedSince(startedMs);
461
+ const failures = Math.min(claim.consecutiveFailures + 1, 5);
462
+ try {
463
+ await repo.failMediaUsageCleanup({
464
+ leaseToken,
465
+ retryDelaySeconds: failureDelayMs(failures) / 1e3,
466
+ consecutiveFailures: failures,
467
+ durationMs,
468
+ errorCode: "MEDIA_USAGE_CLEANUP_FAILED"
469
+ });
470
+ } catch (failureError) {
471
+ console.error("[media-usage-cleanup] Failed to record cleanup failure:", failureError);
472
+ }
473
+ console.error("[media-usage-cleanup] Cleanup failed:", error);
474
+ return {
475
+ ...emptyResult("failed", durationMs),
476
+ candidateRows,
477
+ deletedRows: deletedOrphans + deletedStale + deletedAbandoned,
478
+ deletedOrphans,
479
+ deletedStale,
480
+ deletedAbandoned,
481
+ deletedWriteLeases,
482
+ backlogLowerBound,
483
+ scanHasMore
484
+ };
485
+ }
486
+ }
487
+ function selectCleanupCandidates(candidates, activeLeaseAt) {
488
+ const orphanIds = [];
489
+ const staleIds = [];
490
+ const abandonedIds = [];
491
+ const entries = [];
492
+ for (const candidate of candidates) {
493
+ if (hasActiveWriteLease(candidate, activeLeaseAt)) {
494
+ entries.push({
495
+ candidate,
496
+ target: null
497
+ });
498
+ continue;
499
+ }
500
+ const target = cleanupTarget(candidate);
501
+ if (target === null) {
502
+ entries.push({
503
+ candidate,
504
+ target: null
505
+ });
506
+ continue;
507
+ }
508
+ if (orphanIds.length + staleIds.length + abandonedIds.length >= MEDIA_USAGE_CLEANUP_DELETE_LIMIT) break;
509
+ if (target === "orphan") orphanIds.push(candidate.id);
510
+ if (target === "stale") staleIds.push(candidate.id);
511
+ if (target === "abandoned") abandonedIds.push(candidate.id);
512
+ entries.push({
513
+ candidate,
514
+ target
515
+ });
516
+ }
517
+ return {
518
+ orphanIds,
519
+ staleIds,
520
+ abandonedIds,
521
+ entries
522
+ };
523
+ }
524
+ function cleanupTarget(candidate) {
525
+ if (candidate.currentGeneration === null) return "orphan";
526
+ if (candidate.currentGeneration === candidate.generation || candidate.indexedAt === null) return null;
527
+ return candidate.createdAt < candidate.indexedAt ? "stale" : "abandoned";
528
+ }
529
+ function hasActiveWriteLease(candidate, activeLeaseAt) {
530
+ return candidate.writeLeaseExpiresAt !== null && candidate.writeLeaseExpiresAt > activeLeaseAt;
531
+ }
532
+ function cursorFor(candidate) {
533
+ return {
534
+ createdAt: candidate.createdAt,
535
+ id: candidate.id
536
+ };
537
+ }
538
+ function cursorAfterCompletedCandidates(selected, completedTargets, priorCursor) {
539
+ let cursor = priorCursor;
540
+ for (const entry of selected.entries) {
541
+ if (entry.target !== null && !completedTargets.has(entry.target)) break;
542
+ cursor = cursorFor(entry.candidate);
543
+ }
544
+ return cursor;
545
+ }
546
+ function emptyResult(status, durationMs) {
547
+ return {
548
+ status,
549
+ candidateRows: 0,
550
+ deletedRows: 0,
551
+ deletedOrphans: 0,
552
+ deletedStale: 0,
553
+ deletedAbandoned: 0,
554
+ deletedWriteLeases: 0,
555
+ backlogLowerBound: 0,
556
+ scanHasMore: false,
557
+ durationMs
558
+ };
559
+ }
560
+ function withinBudget(startedMs) {
561
+ return elapsedSince(startedMs) < MEDIA_USAGE_CLEANUP_TIME_BUDGET_MS;
562
+ }
563
+ function elapsedSince(startedMs) {
564
+ return Math.max(0, Date.now() - startedMs);
565
+ }
566
+ function failureDelayMs(consecutiveFailures) {
567
+ return Math.min(2 ** (consecutiveFailures - 1), 15) * MEDIA_USAGE_CLEANUP_INTERVAL_MS;
568
+ }
569
+ function cleanupLease(leaseToken) {
570
+ return { leaseToken };
571
+ }
572
+
573
+ //#endregion
574
+ //#region src/cleanup.ts
575
+ /**
576
+ * System cleanup
577
+ *
578
+ * Runs periodic maintenance tasks that prevent unbounded accumulation of
579
+ * expired or stale data. Called from cron scheduler ticks and (for latency-
580
+ * sensitive subsystems) inline during relevant requests.
581
+ *
582
+ * Each subsystem cleanup is independent and non-fatal -- if one fails, the
583
+ * rest still run. Failures are logged but never surface to callers.
584
+ */
585
+ const REVISION_KEEP_COUNT = 50;
586
+ const REVISION_PRUNE_BATCH_SIZE = 10;
587
+ /** Target cadence for full system cleanup. */
588
+ const SYSTEM_CLEANUP_INTERVAL_MS = 3600 * 1e3;
589
+ /**
590
+ * Tolerance when comparing the last cleanup time to the interval. Allows
591
+ * a tick that fires slightly before the exact hour to still be treated as
592
+ * the hourly cleanup when the previous one ran at the start of the hour.
593
+ */
594
+ const SYSTEM_CLEANUP_INTERVAL_TOLERANCE_MS = 300 * 1e3;
595
+ /**
596
+ * Decide whether the full system cleanup should run for this tick.
597
+ *
598
+ * Bookkeeping cleanup is expensive on serverless free tiers with tight CPU
599
+ * budgets, so it runs once per hour on the top of the hour. The per-minute
600
+ * path keeps only scheduled publishing, cron tasks, and the heartbeat.
601
+ */
602
+ function shouldRunSystemCleanup(currentTime, lastRunAt) {
603
+ if (currentTime.getUTCMinutes() !== 0) return false;
604
+ if (!lastRunAt) return true;
605
+ return currentTime.getTime() - lastRunAt.getTime() >= SYSTEM_CLEANUP_INTERVAL_MS - SYSTEM_CLEANUP_INTERVAL_TOLERANCE_MS;
606
+ }
607
+ /**
608
+ * Run all system cleanup tasks.
609
+ *
610
+ * Safe to call frequently -- each subsystem tolerates repeated calls, and
611
+ * repeated calls with nothing to clean are cheap.
612
+ *
613
+ * @param db - The database instance
614
+ * @param storage - Optional storage backend for deleting orphaned files.
615
+ * When omitted, pending upload DB rows are still deleted but the
616
+ * corresponding files in object storage are not removed.
617
+ */
618
+ async function runSystemCleanup(db, storage) {
619
+ const result = {
620
+ challenges: -1,
621
+ expiredTokens: -1,
622
+ pendingUploads: -1,
623
+ pendingUploadFiles: -1,
624
+ uploadAttempts: -1,
625
+ revisionsPruned: -1,
626
+ mediaUsage: -1,
627
+ notFoundLog: -1,
628
+ transferStaging: -1
629
+ };
630
+ try {
631
+ result.challenges = await cleanupExpiredChallenges(db);
632
+ } catch (error) {
633
+ console.error("[cleanup] Failed to clean expired challenges:", error);
634
+ }
635
+ try {
636
+ await createKyselyAdapter(db).deleteExpiredTokens();
637
+ result.expiredTokens = 0;
638
+ } catch (error) {
639
+ console.error("[cleanup] Failed to clean expired tokens:", error);
640
+ }
641
+ try {
642
+ const orphanedKeys = await new MediaRepository(db).cleanupPendingUploads();
643
+ result.pendingUploads = orphanedKeys.length;
644
+ if (storage && orphanedKeys.length > 0) {
645
+ let filesDeleted = 0;
646
+ for (const key of orphanedKeys) try {
647
+ await storage.delete(key);
648
+ filesDeleted++;
649
+ } catch (error) {
650
+ console.error(`[cleanup] Failed to delete storage file ${key}:`, error);
651
+ }
652
+ result.pendingUploadFiles = filesDeleted;
653
+ } else result.pendingUploadFiles = 0;
654
+ } catch (error) {
655
+ console.error("[cleanup] Failed to clean pending uploads:", error);
656
+ }
657
+ try {
658
+ const mediaRepo = new MediaRepository(db);
659
+ const completedAttemptsDeleted = await mediaRepo.deleteCompletedUploadAttempts();
660
+ if (!storage) result.uploadAttempts = completedAttemptsDeleted;
661
+ else {
662
+ const storageKeys = await mediaRepo.findUploadAttemptsForCleanup();
663
+ let attemptsDeleted = completedAttemptsDeleted;
664
+ for (const storageKey of storageKeys) if (await removeUploadAttempt(storage, mediaRepo, storageKey)) attemptsDeleted++;
665
+ result.uploadAttempts = attemptsDeleted;
666
+ }
667
+ } catch (error) {
668
+ console.error("[cleanup] Failed to clean media upload attempts:", error);
669
+ }
670
+ try {
671
+ result.revisionsPruned = await pruneQueuedRevisions(db);
672
+ } catch (error) {
673
+ console.error("[cleanup] Failed to prune revisions:", error);
674
+ }
675
+ try {
676
+ const mediaUsage = await cleanupMediaUsage(db);
677
+ result.mediaUsage = mediaUsage.status === "failed" ? -1 : mediaUsage.deletedRows;
678
+ } catch (error) {
679
+ console.error("[cleanup] Failed to clean media usage:", error);
680
+ }
681
+ try {
682
+ result.notFoundLog = await new RedirectRepository(db).cleanup404Log();
683
+ } catch (error) {
684
+ console.error("[cleanup] Failed to cap the 404 log:", error);
685
+ }
686
+ if (storage) try {
687
+ const { collectTransferStaging } = await import("./gc-ByyAYiYE.mjs");
688
+ result.transferStaging = (await collectTransferStaging(db, storage)).collected;
689
+ } catch (error) {
690
+ console.error("[transfer] Failed to collect transfer staging:", error);
691
+ }
692
+ else result.transferStaging = 0;
693
+ return result;
694
+ }
695
+ async function pruneQueuedRevisions(db) {
696
+ const queued = await db.selectFrom("_emdash_revision_prune_queue").selectAll().orderBy("revision_id").limit(REVISION_PRUNE_BATCH_SIZE).execute();
697
+ const revisionRepo = new RevisionRepository(db);
698
+ let totalPruned = 0;
699
+ for (const row of queued) try {
700
+ totalPruned += await revisionRepo.pruneQueuedEntry(row.collection, row.entry_id, row.revision_id, REVISION_KEEP_COUNT);
701
+ } catch (error) {
702
+ console.error(`[cleanup] Failed to prune revisions for ${row.collection}/${row.entry_id}:`, error);
703
+ }
704
+ return totalPruned;
705
+ }
706
+
707
+ //#endregion
708
+ //#region src/comments/moderator.ts
709
+ /** Plugin ID for the built-in default comment moderator */
710
+ const DEFAULT_COMMENT_MODERATOR_PLUGIN_ID = "emdash-default-comment-moderator";
711
+ /**
712
+ * The comment:moderate handler for the built-in default moderator.
713
+ */
714
+ async function defaultCommentModerate(event, _ctx) {
715
+ const { comment, collectionSettings, priorApprovedCount } = event;
716
+ if (collectionSettings.commentsAutoApproveUsers && comment.authorUserId) return {
717
+ status: "approved",
718
+ reason: "Authenticated CMS user"
719
+ };
720
+ if (collectionSettings.commentsModeration === "none") return {
721
+ status: "approved",
722
+ reason: "Moderation disabled"
723
+ };
724
+ if (collectionSettings.commentsModeration === "first_time" && priorApprovedCount > 0) return {
725
+ status: "approved",
726
+ reason: "Returning commenter"
727
+ };
728
+ return {
729
+ status: "pending",
730
+ reason: "Held for review"
731
+ };
732
+ }
733
+
734
+ //#endregion
735
+ //#region src/scheduled-publish.ts
736
+ /**
737
+ * Default cap on items promoted per collection in a single sweep. Bounds the
738
+ * publish/webhook fan-out of one tick so a large backlog can't exhaust a Worker
739
+ * invocation's CPU/subrequest budget; the remainder drains on later ticks.
740
+ */
741
+ const SCHEDULED_PUBLISH_BATCH_LIMIT = 100;
742
+ const SCHEDULED_PUBLISH_CURSOR_PREFIX = "emdash:scheduled-publish-cursor:";
743
+ function scheduledPublishCursorKey(collection) {
744
+ return `${SCHEDULED_PUBLISH_CURSOR_PREFIX}${collection}`;
745
+ }
746
+ function isScheduledPublishCursor(value) {
747
+ return typeof value === "object" && value !== null && "scheduledAt" in value && typeof value.scheduledAt === "string" && "id" in value && typeof value.id === "string";
748
+ }
749
+ /**
750
+ * Publish every content item whose `scheduled_at` is in the past.
751
+ *
752
+ * Iterates all collections, finds due items (`findReadyToPublish` returns both
753
+ * scheduled drafts and published entries with pending scheduled changes), and
754
+ * publishes each. `publish()` clears `scheduled_at`, so a second sweep is a
755
+ * no-op — safe to run on every tick.
756
+ *
757
+ * Bounded per collection by `limit` (default `SCHEDULED_PUBLISH_BATCH_LIMIT`):
758
+ * a large backlog drains across successive ticks rather than in one unbounded
759
+ * pass. After each collection's batch, `onPublished` (if given) is awaited so
760
+ * cache-tag invalidation happens incrementally, not just at the very end.
761
+ *
762
+ * Returns every item it promoted so request-less callers (the Cloudflare
763
+ * `scheduled()` handler) can also act on the full set.
764
+ */
765
+ async function publishDueContent(db, options = {}) {
766
+ const { publish, onPublished, limit = SCHEDULED_PUBLISH_BATCH_LIMIT, currentTime = /* @__PURE__ */ new Date() } = options;
767
+ const published = [];
768
+ let collections;
769
+ try {
770
+ collections = await new SchemaRegistry(db).listCollections();
771
+ } catch (error) {
772
+ console.error("[scheduled-publish] Failed to list collections:", error);
773
+ return published;
774
+ }
775
+ const repo = new ContentRepository(db);
776
+ const optionsRepo = new OptionsRepository(db);
777
+ const doPublish = publish ?? ((collection, id, opts) => handleContentPublish(db, collection, id, opts));
778
+ const batchLimit = limit > 0 ? limit : void 0;
779
+ for (const collection of collections) try {
780
+ let cursor;
781
+ let cursorRevision = null;
782
+ if (batchLimit !== void 0) try {
783
+ const stored = await optionsRepo.getVersioned(scheduledPublishCursorKey(collection.slug));
784
+ cursorRevision = stored?.revision ?? null;
785
+ if (isScheduledPublishCursor(stored?.value)) cursor = stored.value;
786
+ } catch (error) {
787
+ console.error(`[scheduled-publish] Failed to read cursor for "${collection.slug}":`, error);
788
+ }
789
+ let due = await repo.findReadyToPublish(collection.slug, batchLimit, currentTime, cursor);
790
+ if (cursor && due.length === 0) due = await repo.findReadyToPublish(collection.slug, batchLimit, currentTime);
791
+ const batch = [];
792
+ for (const item of due) {
793
+ const publishedAt = item.publishedAt == null ? item.scheduledAt ?? void 0 : void 0;
794
+ const result = await doPublish(collection.slug, item.id, {
795
+ publishedAt,
796
+ requireScheduledDue: true,
797
+ expectedScheduledAt: item.scheduledAt ?? void 0,
798
+ currentTime
799
+ });
800
+ if (result.success) batch.push({
801
+ collection: collection.slug,
802
+ id: item.id
803
+ });
804
+ else if (result.error?.code === "NOT_DUE" || result.error?.code === "PUBLISH_REJECTED") {} else console.error(`[scheduled-publish] Failed to publish ${collection.slug}/${item.id}:`, result.error);
805
+ }
806
+ const lastAttempted = due.at(-1);
807
+ if (batchLimit !== void 0 && lastAttempted?.scheduledAt) try {
808
+ await optionsRepo.compareAndSet(scheduledPublishCursorKey(collection.slug), cursorRevision, {
809
+ scheduledAt: lastAttempted.scheduledAt,
810
+ id: lastAttempted.id
811
+ });
812
+ } catch (error) {
813
+ console.error(`[scheduled-publish] Failed to save cursor for "${collection.slug}":`, error);
814
+ }
815
+ if (batch.length > 0) {
816
+ published.push(...batch);
817
+ if (onPublished) try {
818
+ await onPublished(batch);
819
+ } catch (error) {
820
+ console.error(`[scheduled-publish] onPublished failed after "${collection.slug}" batch:`, error);
821
+ }
822
+ }
823
+ } catch (error) {
824
+ console.error(`[scheduled-publish] Sweep failed for "${collection.slug}":`, error);
825
+ }
826
+ return published;
827
+ }
828
+
829
+ //#endregion
830
+ //#region src/emdash-runtime.ts
831
+ /**
832
+ * EmDashRuntime - Core runtime for EmDash CMS
833
+ *
834
+ * Manages database, storage, plugins (trusted + sandboxed), hooks, and
835
+ * provides handlers for content/media operations.
836
+ *
837
+ * Created once per worker lifetime, cached and reused across requests.
838
+ */
839
+ const LEADING_SLASH_PATTERN = /^\//;
840
+ const LOCALE_CASING_REPAIR_OPTION = "emdash:repair_locale_casing";
841
+ const SETUP_COMPLETE_OPTION = "emdash:setup_complete";
842
+ const PLUGIN_COMMENT_STATUSES = new Set([
843
+ "approved",
844
+ "pending",
845
+ "spam"
846
+ ]);
847
+ function assertPluginCommentStatus(value, name) {
848
+ if (!PLUGIN_COMMENT_STATUSES.has(value)) throw Object.assign(/* @__PURE__ */ new Error(`${name} must be one of: approved, pending, spam`), { code: "COMMENT_STATUS_INVALID" });
849
+ }
850
+ function getLocaleCasingRepairVersion(locales) {
851
+ if (locales.length === 0) return null;
852
+ return `1:${locales.toSorted().join(",")}`;
853
+ }
854
+ /**
855
+ * Parse a JSON column expected to contain an array of strings.
856
+ *
857
+ * Throws on malformed JSON rather than returning []; callers are responsible
858
+ * for deciding how to handle/log the error. Empty string / null inputs return
859
+ * [] (they represent "no value"). Non-string array entries are filtered out.
860
+ */
861
+ function parseStringArray(raw) {
862
+ if (!raw) return [];
863
+ const parsed = JSON.parse(raw);
864
+ if (!Array.isArray(parsed)) return [];
865
+ return parsed.filter((v) => typeof v === "string");
866
+ }
867
+ function isModerationDecision(value) {
868
+ if (typeof value !== "object" || value === null || !("status" in value)) return false;
869
+ if (value.status !== "pending" && value.status !== "approved" && value.status !== "spam") return false;
870
+ return !("reason" in value) || value.reason === void 0 || typeof value.reason === "string";
871
+ }
872
+ const DRAFT_ONLY_UPDATE_KEYS = new Set([
873
+ "data",
874
+ "slug",
875
+ "locale",
876
+ "skipRevision",
877
+ "references",
878
+ "actor",
879
+ "migrateBlocks",
880
+ "replaceBlocks"
881
+ ]);
882
+ /** Field types whose schema is an array, so a stored blank string can never validate. */
883
+ const ARRAY_FIELD_TYPES = new Set([
884
+ "portableText",
885
+ "multiSelect",
886
+ "repeater"
887
+ ]);
888
+ const MAX_DRAFT_STAGE_ATTEMPTS = 32;
889
+ const PLUGIN_INVOCATION_RELEASE_GRACE_MS = 6e4;
890
+ /**
891
+ * A `ContentSaveRejectedError` carries a message the plugin wrote for the
892
+ * editor; every other exception stays internal and is replaced by a generic
893
+ * message so hook internals cannot leak through the API.
894
+ */
895
+ function beforeSaveFailure(error) {
896
+ if (isContentSaveRejection(error)) {
897
+ const sandbox = getSandboxSaveRejectionDetails(error);
898
+ if (sandbox) return {
899
+ success: false,
900
+ error: {
901
+ code: ErrorCode.SAVE_REJECTED,
902
+ message: error.message,
903
+ details: sandbox
904
+ }
905
+ };
906
+ return {
907
+ success: false,
908
+ error: {
909
+ code: ErrorCode.SAVE_REJECTED,
910
+ message: error.message
911
+ }
912
+ };
913
+ }
914
+ console.error("EmDash: content:beforeSave hook failed:", error);
915
+ return {
916
+ success: false,
917
+ error: {
918
+ code: ErrorCode.CONTENT_HOOK_ERROR,
919
+ message: "A plugin hook failed while saving content"
920
+ }
921
+ };
922
+ }
923
+ /**
924
+ * Convert a ContentItem to Record<string, unknown> for hook consumption.
925
+ * Hooks receive the full item as a flat record.
926
+ */
927
+ function contentItemToRecord(item) {
928
+ return { ...item };
929
+ }
930
+ function toPluginContentItem(item) {
931
+ return {
932
+ id: item.id,
933
+ type: item.type,
934
+ slug: item.slug,
935
+ status: item.status,
936
+ locale: item.locale,
937
+ data: item.data,
938
+ createdAt: item.createdAt,
939
+ updatedAt: item.updatedAt,
940
+ publishedAt: item.publishedAt,
941
+ scheduledAt: item.scheduledAt,
942
+ authorId: item.authorId,
943
+ translationGroup: item.translationGroup,
944
+ liveRevisionId: item.liveRevisionId,
945
+ draftRevisionId: item.draftRevisionId,
946
+ version: item.version,
947
+ ...item.seo === void 0 ? {} : { seo: item.seo }
948
+ };
949
+ }
950
+ /**
951
+ * Db init lock reclaim deadline. Derived from the migration race wait so
952
+ * they can't drift apart: a healthy init can legitimately block for the
953
+ * full MIGRATION_RACE_WAIT_MS inside waitForConcurrentMigrator, plus cold
954
+ * connect and migrator work, before it should be presumed dead. The outer
955
+ * runtime init lock (middleware.ts) must use a strictly larger deadline —
956
+ * it wraps create() → getDatabase() → this lock, and equal deadlines would
957
+ * let the outer reclaim while the inner is legitimately still working.
958
+ */
959
+ const DB_INIT_DEADLINE_MS = MIGRATION_RACE_WAIT_MS + 2e4;
960
+ /**
961
+ * Db cache + its init lock live on globalThis behind a Symbol: the bundler
962
+ * can duplicate this module across SSR chunks (same reasoning as
963
+ * request-cache.ts), and a duplicated cache/lock would mean concurrent
964
+ * independent db inits — and duplicate migrators — per isolate.
965
+ */
966
+ const DB_HOLDER_KEY = Symbol.for("emdash:db-cache");
967
+ const globalSymbolStore = globalThis;
968
+ function getDbHolder() {
969
+ let holder = globalSymbolStore[DB_HOLDER_KEY];
970
+ if (!holder) {
971
+ holder = {
972
+ cache: /* @__PURE__ */ new Map(),
973
+ lock: createInitLock(),
974
+ failures: /* @__PURE__ */ new Map()
975
+ };
976
+ globalSymbolStore[DB_HOLDER_KEY] = holder;
977
+ }
978
+ holder.failures ??= /* @__PURE__ */ new Map();
979
+ return holder;
980
+ }
981
+ /**
982
+ * After a database init fails (migrations threw), skip re-attempting for
983
+ * this long. Cold isolates still get one attempt each, so a transient
984
+ * failure heals on its own; a persistently failing migration is retried at
985
+ * most once per backoff window per isolate instead of on every request.
986
+ */
987
+ const DB_INIT_FAILURE_BACKOFF_MS = 3e4;
988
+ /**
989
+ * Auto-seed runs at most once per isolate per database. Its lock + "done" set
990
+ * live on globalThis (same bundler-duplication reasoning as the db cache) so a
991
+ * reclaimed-and-rerun `create()` can't seed a second time concurrently. The
992
+ * lock polls rather than sharing a promise, so it is safe to await across a
993
+ * cancelled owner in workerd.
994
+ */
995
+ const SEED_HOLDER_KEY = Symbol.for("emdash:seed-state");
996
+ function getSeedHolder() {
997
+ let holder = globalSymbolStore[SEED_HOLDER_KEY];
998
+ if (!holder) {
999
+ holder = {
1000
+ done: /* @__PURE__ */ new Set(),
1001
+ lock: createInitLock()
1002
+ };
1003
+ globalSymbolStore[SEED_HOLDER_KEY] = holder;
1004
+ }
1005
+ return holder;
1006
+ }
1007
+ const storageCache = /* @__PURE__ */ new Map();
1008
+ const sandboxedPluginCache = /* @__PURE__ */ new Map();
1009
+ const sandboxedManifestCache = /* @__PURE__ */ new Map();
1010
+ /**
1011
+ * Per-tier sets of `${pluginId}:${version}` keys present in
1012
+ * `sandboxedPluginCache`. Used during sync to know which entries belong
1013
+ * to which install source so we can invalidate only what belongs to the
1014
+ * tier currently being synced.
1015
+ */
1016
+ const marketplacePluginKeys = /* @__PURE__ */ new Set();
1017
+ const registryPluginKeys = /* @__PURE__ */ new Set();
1018
+ /**
1019
+ * Manifest metadata for runtime-installed sandboxed plugins (marketplace
1020
+ * and registry both). Keyed by `pluginId`; readers don't care which
1021
+ * source the plugin came from. Named `marketplace*` for legacy reasons.
1022
+ */
1023
+ const marketplaceManifestCache = /* @__PURE__ */ new Map();
1024
+ /** Route metadata for sandboxed plugins: pluginId -> routeName -> RouteMeta */
1025
+ const sandboxedRouteMetaCache = /* @__PURE__ */ new Map();
1026
+ let sandboxRunner = null;
1027
+ function allowedBrowserImageHosts(capabilities, allowedHosts) {
1028
+ if (capabilities.includes("network:request:unrestricted") || capabilities.includes("network:fetch:any")) return ["*"];
1029
+ if (capabilities.includes("network:request") || capabilities.includes("network:fetch")) return allowedHosts;
1030
+ return [];
1031
+ }
1032
+ /**
1033
+ * EmDashRuntime - singleton per worker
1034
+ */
1035
+ var EmDashRuntime = class EmDashRuntime {
1036
+ /**
1037
+ * The singleton database instance (worker-lifetime cached).
1038
+ * Use the `db` getter instead — it checks the request context first
1039
+ * for per-request overrides (D1 read replica sessions, DO multi-site).
1040
+ */
1041
+ _db;
1042
+ storage;
1043
+ configuredPlugins;
1044
+ sandboxedPlugins;
1045
+ sandboxedPluginEntries;
1046
+ /**
1047
+ * Schema registry bound to the current request/event-scoped connection.
1048
+ * Built per access (SchemaRegistry just wraps a db) against `this.db`, the
1049
+ * ALS-aware getter — never a captured snapshot of the singleton. On a
1050
+ * connection-backed adapter (Postgres over Hyperdrive) a captured singleton
1051
+ * would query a socket opened by an earlier event and trip workerd's
1052
+ * cross-request I/O guard; the catch in handlers like handleContentUpdate
1053
+ * would then silently treat a revision-enabled collection as non-revisioned
1054
+ * and write draft edits to live columns. Same reasoning as the per-call
1055
+ * registry in _buildManifest().
1056
+ */
1057
+ get schemaRegistry() {
1058
+ return new SchemaRegistry(this.db);
1059
+ }
1060
+ _hooks;
1061
+ config;
1062
+ mediaProviders;
1063
+ mediaProviderEntries;
1064
+ cronExecutor;
1065
+ email;
1066
+ cronScheduler;
1067
+ enabledPlugins;
1068
+ pluginStates;
1069
+ activePluginContentActions = /* @__PURE__ */ new Map();
1070
+ pendingPluginAfterHooks = /* @__PURE__ */ new Map();
1071
+ activePluginInvocations = /* @__PURE__ */ new Set();
1072
+ /** Timed-out sandbox invocations whose later actions must schedule hooks without another flush. */
1073
+ releasedPluginInvocations = /* @__PURE__ */ new Set();
1074
+ pluginInvocationCacheInvalidators = /* @__PURE__ */ new Map();
1075
+ pluginContentCacheInvalidator;
1076
+ /**
1077
+ * Isolate-lifetime guard so FTS indexes are verified at most once per
1078
+ * worker rather than on every admin request. See ensureSearchHealthy().
1079
+ * Uses the poison-immune single-flight cache (never a shared awaitable
1080
+ * promise) so a cancelled first caller can't wedge later ones.
1081
+ */
1082
+ _searchHealthCache = createSingleFlightCache();
1083
+ /** Current hook pipeline. Use the `hooks` getter for external access. */
1084
+ get hooks() {
1085
+ return this._hooks;
1086
+ }
1087
+ /** All plugins eligible for the hook pipeline (includes built-in plugins).
1088
+ * Stored so we can rebuild the pipeline when plugins are enabled/disabled. */
1089
+ allPipelinePlugins;
1090
+ /** Guards the once-per-process plugin storage-index sync. */
1091
+ storageIndexesSynced = false;
1092
+ /** Factory options for the hook pipeline context factory */
1093
+ pipelineFactoryOptions;
1094
+ /** Dependencies needed for exclusive hook resolution */
1095
+ runtimeDeps;
1096
+ /** Mutable ref for the cron invokeCronHook closure to read the current pipeline */
1097
+ pipelineRef;
1098
+ commentModerationInProgress = /* @__PURE__ */ new Set();
1099
+ /**
1100
+ * Get the database instance for the current request.
1101
+ *
1102
+ * Checks the ALS-based request context first — middleware sets a
1103
+ * per-request Kysely instance there for D1 read replica sessions
1104
+ * or DO preview databases. Falls back to the singleton instance.
1105
+ */
1106
+ get db() {
1107
+ const ctx = getRequestContext();
1108
+ if (ctx?.db) return ctx.db;
1109
+ return this._db;
1110
+ }
1111
+ /** Bind the platform cache provider used by plugin actions, including request-less cron hooks. */
1112
+ setPluginContentCacheInvalidator(invalidateContentCache) {
1113
+ this.pluginContentCacheInvalidator = invalidateContentCache;
1114
+ }
1115
+ constructor(parts) {
1116
+ this._db = parts.db;
1117
+ this.storage = parts.storage;
1118
+ this.configuredPlugins = parts.configuredPlugins;
1119
+ this.sandboxedPlugins = parts.sandboxedPlugins;
1120
+ this.sandboxedPluginEntries = parts.sandboxedPluginEntries;
1121
+ this._hooks = parts.hooks;
1122
+ this.enabledPlugins = parts.enabledPlugins;
1123
+ this.pluginStates = parts.pluginStates;
1124
+ this.config = parts.config;
1125
+ this.mediaProviders = parts.mediaProviders;
1126
+ this.mediaProviderEntries = parts.mediaProviderEntries;
1127
+ this.cronExecutor = parts.cronExecutor;
1128
+ this.cronScheduler = parts.cronScheduler;
1129
+ this.email = parts.emailPipeline;
1130
+ this.allPipelinePlugins = parts.allPipelinePlugins;
1131
+ this.pipelineFactoryOptions = parts.pipelineFactoryOptions;
1132
+ this.runtimeDeps = parts.runtimeDeps;
1133
+ this.pipelineRef = parts.pipelineRef;
1134
+ }
1135
+ /**
1136
+ * Get the sandbox runner instance (for marketplace install/update)
1137
+ */
1138
+ getSandboxRunner() {
1139
+ return sandboxRunner;
1140
+ }
1141
+ /**
1142
+ * Whether the sandbox bypass mode (sandbox: false) is active.
1143
+ * Marketplace install/update handlers use this to skip the
1144
+ * SANDBOX_NOT_AVAILABLE gate, since the bypass path loads
1145
+ * marketplace plugins in-process via syncMarketplacePlugins().
1146
+ */
1147
+ isSandboxBypassed() {
1148
+ return this.runtimeDeps.sandboxBypassed === true;
1149
+ }
1150
+ /**
1151
+ * Publish any content whose scheduled time has passed.
1152
+ * Returns the items promoted so callers can invalidate their cache tags.
1153
+ */
1154
+ async publishScheduled() {
1155
+ return this.publishScheduledWithFence();
1156
+ }
1157
+ async publishScheduledWithFence(onPublished) {
1158
+ const recordWrite = await assertSiteWriteAllowed(this.db);
1159
+ const currentTime = this.runtimeDeps.now?.() ?? /* @__PURE__ */ new Date();
1160
+ const published = await publishDueContent(this.db, {
1161
+ publish: (collection, id, options) => this.handleContentPublish(collection, id, {
1162
+ ...options,
1163
+ origin: { source: "scheduler" }
1164
+ }),
1165
+ onPublished,
1166
+ currentTime
1167
+ });
1168
+ if (published.length > 0) await recordWrite();
1169
+ return published;
1170
+ }
1171
+ /**
1172
+ * Run the scheduled-maintenance batch: cron tasks, scheduled publishing,
1173
+ * and hourly system cleanup. For request-less drivers — the Cloudflare
1174
+ * `scheduled()` handler invokes this from a Cron Trigger. (On Node the
1175
+ * timer-based scheduler drives the same work itself.)
1176
+ *
1177
+ * Full cleanup runs only on the top of the hour so per-minute work stays
1178
+ * proportional to what is due; the heartbeat and scheduled publish sweep
1179
+ * still run every tick.
1180
+ *
1181
+ * Each step is independent and non-fatal. Returns the content promoted
1182
+ * by the publishing sweep so the caller can purge edge-cache tags.
1183
+ *
1184
+ * `onPublished` (optional) is awaited after each collection's batch so a
1185
+ * request-less driver can invalidate edge-cache tags incrementally rather
1186
+ * than only after the whole sweep — bounding stale-cache exposure if the
1187
+ * runtime is killed mid-sweep.
1188
+ */
1189
+ async runScheduledTasks(options = {}) {
1190
+ const { published } = await this.runScheduledTasksWithStats(options);
1191
+ return { published };
1192
+ }
1193
+ async runScheduledTasksWithStats(options = {}) {
1194
+ if (options.invalidateContentCache) this.pluginContentCacheInvalidator = options.invalidateContentCache;
1195
+ let processed = 0;
1196
+ if (this.cronExecutor) {
1197
+ try {
1198
+ processed = await this.cronExecutor.tick();
1199
+ } catch (error) {
1200
+ console.error("[cron] Tick failed:", error);
1201
+ }
1202
+ try {
1203
+ await this.cronExecutor.recoverStaleLocks();
1204
+ } catch (error) {
1205
+ console.error("[cron] Stale lock recovery failed:", error);
1206
+ }
1207
+ }
1208
+ const currentTime = this.runtimeDeps.now?.() ?? /* @__PURE__ */ new Date();
1209
+ const { published } = await this.runScheduledMaintenanceTick(options, currentTime);
1210
+ return {
1211
+ processed,
1212
+ published
1213
+ };
1214
+ }
1215
+ /**
1216
+ * Time-gated maintenance pass shared by Cloudflare `scheduled()` and the
1217
+ * Node timer scheduler. Scheduled publishing and the heartbeat run every
1218
+ * tick; the heavier bookkeeping cleanups run only on the top of the hour so
1219
+ * per-minute work stays proportional to what is actually due. This keeps the
1220
+ * cold-start CPU budget on Workers Free from being consumed by cleanup
1221
+ * queries on every quiet-hours tick.
1222
+ */
1223
+ async runScheduledMaintenanceTick(options, currentTime) {
1224
+ let published = [];
1225
+ try {
1226
+ published = await this.publishScheduledWithFence(options.onPublished);
1227
+ } catch (error) {
1228
+ console.error("[scheduled-publish] Sweep failed:", error);
1229
+ }
1230
+ await this.runSystemCleanupIfDue(currentTime);
1231
+ try {
1232
+ await this.syncPluginStorageIndexesOnce();
1233
+ } catch (error) {
1234
+ console.error("[plugins] Storage index sync failed:", error);
1235
+ }
1236
+ await maybeRunScheduledBackup(this.db, this.storage ?? void 0);
1237
+ await recordSchedulerHeartbeatSafely(this.db, currentTime);
1238
+ return { published };
1239
+ }
1240
+ lastSystemCleanupAt = null;
1241
+ shouldRunSystemCleanup(currentTime) {
1242
+ return shouldRunSystemCleanup(currentTime, this.lastSystemCleanupAt);
1243
+ }
1244
+ async runSystemCleanupIfDue(currentTime) {
1245
+ if (!this.shouldRunSystemCleanup(currentTime)) return;
1246
+ this.lastSystemCleanupAt = currentTime;
1247
+ try {
1248
+ await runSystemCleanup(this.db, this.storage ?? void 0);
1249
+ } catch (error) {
1250
+ console.error("[cleanup] System cleanup failed:", error);
1251
+ }
1252
+ }
1253
+ /**
1254
+ * Materialize plugin-declared storage indexes, once per process.
1255
+ *
1256
+ * Called from the scheduler path, not from request handlers — configured
1257
+ * plugins have no install handler, so the tick is their only sync moment.
1258
+ */
1259
+ async syncPluginStorageIndexesOnce() {
1260
+ if (this.storageIndexesSynced) return;
1261
+ this.storageIndexesSynced = true;
1262
+ await syncDeclaredStorageIndexes(this.db, [...this.allPipelinePlugins, ...marketplaceManifestCache.values()]);
1263
+ }
1264
+ /**
1265
+ * Stop the cron scheduler gracefully.
1266
+ * Call during worker shutdown or hot-reload.
1267
+ */
1268
+ async stopCron() {
1269
+ if (this.cronScheduler) await this.cronScheduler.stop();
1270
+ }
1271
+ /** Stop background work and discard loaded sandbox isolates. */
1272
+ async shutdown() {
1273
+ await this.stopCron();
1274
+ sandboxRunner?.setCommentModerate?.(null);
1275
+ await sandboxRunner?.terminateAll();
1276
+ sandboxRunner = null;
1277
+ sandboxedPluginCache.clear();
1278
+ sandboxedManifestCache.clear();
1279
+ sandboxedRouteMetaCache.clear();
1280
+ marketplaceManifestCache.clear();
1281
+ marketplacePluginKeys.clear();
1282
+ registryPluginKeys.clear();
1283
+ }
1284
+ /**
1285
+ * Update in-memory plugin status and rebuild the hook pipeline.
1286
+ *
1287
+ * Rebuilding the pipeline ensures disabled plugins' hooks stop firing
1288
+ * and re-enabled plugins' hooks start firing again without a restart.
1289
+ * Exclusive hook selections are re-resolved after each rebuild.
1290
+ */
1291
+ async setPluginStatus(pluginId, status) {
1292
+ this.pluginStates.set(pluginId, status);
1293
+ if (status === "active") {
1294
+ this.setSandboxedPluginActive(pluginId, true);
1295
+ this.enabledPlugins.add(pluginId);
1296
+ await this.rebuildHookPipeline();
1297
+ await this._hooks.runPluginActivate(pluginId);
1298
+ } else try {
1299
+ await this._hooks.runPluginDeactivate(pluginId);
1300
+ } finally {
1301
+ this.setSandboxedPluginActive(pluginId, false);
1302
+ this.enabledPlugins.delete(pluginId);
1303
+ await this.rebuildHookPipeline();
1304
+ }
1305
+ }
1306
+ setSandboxedPluginActive(pluginId, active) {
1307
+ for (const [key, plugin] of this.sandboxedPlugins) if (key.slice(0, key.lastIndexOf(":")) === pluginId) plugin.setActive?.(active);
1308
+ }
1309
+ async handlePluginEnable(pluginId) {
1310
+ return enableRuntimePlugin(this, pluginId);
1311
+ }
1312
+ async handlePluginDisable(pluginId) {
1313
+ return disableRuntimePlugin(this, pluginId);
1314
+ }
1315
+ /** Dispatch teardown lifecycle while the runtime-installed isolate is still loaded. */
1316
+ async runPluginUninstallLifecycle(pluginId, deleteData) {
1317
+ if (this.isPluginEnabled(pluginId)) try {
1318
+ await this._hooks.runPluginDeactivate(pluginId);
1319
+ } catch (error) {
1320
+ console.error(`EmDash: Plugin ${pluginId} deactivate hook failed during uninstall:`, error);
1321
+ }
1322
+ try {
1323
+ if ((await this._hooks.runPluginUninstall(pluginId, deleteData)).length > 0) return;
1324
+ await this.findSandboxedPlugin(pluginId)?.invokeHook("plugin:uninstall", { deleteData });
1325
+ } catch (error) {
1326
+ console.error(`EmDash: Plugin ${pluginId} uninstall hook failed:`, error);
1327
+ }
1328
+ }
1329
+ async runPluginInstallLifecycle(pluginId) {
1330
+ await this._hooks.runPluginInstall(pluginId);
1331
+ await this._hooks.runPluginActivate(pluginId);
1332
+ }
1333
+ async runPluginActivateLifecycle(pluginId) {
1334
+ await this._hooks.runPluginActivate(pluginId);
1335
+ }
1336
+ /**
1337
+ * Run install → activate once for each native plugin registered in the
1338
+ * integration config that has no `_plugin_state` row.
1339
+ *
1340
+ * Only the isolate whose insert creates the row runs the hooks. A plugin
1341
+ * whose hooks fail is recorded as inactive rather than retried on later
1342
+ * boots; re-enabling it from the admin runs `plugin:activate`.
1343
+ */
1344
+ async installUnrecordedConfigPlugins(configPlugins, recordedStates) {
1345
+ const stateRepo = new PluginStateRepository(this.db);
1346
+ const failed = [];
1347
+ const recordFailure = (plugin, hook, error) => {
1348
+ console.error(`EmDash: ${hook} failed for config plugin "${plugin.id}"; disabling it:`, error);
1349
+ failed.push(plugin);
1350
+ };
1351
+ for (const plugin of configPlugins) {
1352
+ if (recordedStates.has(plugin.id)) continue;
1353
+ try {
1354
+ if (!await stateRepo.createActiveIfAbsent(plugin.id, plugin.version, "config")) continue;
1355
+ this.pluginStates.set(plugin.id, "active");
1356
+ const installFailure = (await this._hooks.runPluginInstall(plugin.id)).find((result) => !result.success);
1357
+ if (installFailure) {
1358
+ recordFailure(plugin, "plugin:install", installFailure.error);
1359
+ continue;
1360
+ }
1361
+ const activateFailure = (await this._hooks.runPluginActivate(plugin.id)).find((result) => !result.success);
1362
+ if (activateFailure) recordFailure(plugin, "plugin:activate", activateFailure.error);
1363
+ } catch (error) {
1364
+ console.error(`EmDash: Config plugin "${plugin.id}" install/activate failed:`, error);
1365
+ }
1366
+ }
1367
+ if (failed.length === 0) return;
1368
+ for (const plugin of failed) {
1369
+ try {
1370
+ await stateRepo.disable(plugin.id, plugin.version);
1371
+ await setCronTasksEnabled(this.db, plugin.id, false);
1372
+ } catch (error) {
1373
+ console.error(`EmDash: Failed to record config plugin "${plugin.id}" as disabled:`, error);
1374
+ }
1375
+ this.pluginStates.set(plugin.id, "inactive");
1376
+ this.enabledPlugins.delete(plugin.id);
1377
+ }
1378
+ await this.rebuildHookPipeline();
1379
+ }
1380
+ /**
1381
+ * Rebuild the hook pipeline from the current set of enabled plugins.
1382
+ *
1383
+ * Filters `allPipelinePlugins` to only those in `enabledPlugins`,
1384
+ * creates a fresh HookPipeline, re-resolves exclusive hook selections,
1385
+ * and re-wires the context factory so existing references (cron
1386
+ * callbacks, email pipeline) use the new pipeline.
1387
+ */
1388
+ async rebuildHookPipeline() {
1389
+ const newPipeline = createHookPipeline(this.allPipelinePlugins.filter((p) => this.enabledPlugins.has(p.id)), this.pipelineFactoryOptions);
1390
+ await EmDashRuntime.resolveExclusiveHooks(newPipeline, this.db, this.runtimeDeps);
1391
+ if (this.email) newPipeline.setContextFactory({ emailPipeline: this.email });
1392
+ newPipeline.setContextFactory({ cronReschedule: () => this.cronScheduler?.reschedule() });
1393
+ if (this.email) this.email.setPipeline(newPipeline);
1394
+ this.pipelineRef.current = newPipeline;
1395
+ this._hooks = newPipeline;
1396
+ }
1397
+ /**
1398
+ * Synchronize marketplace plugin runtime state with DB + storage.
1399
+ *
1400
+ * Ensures install/update/uninstall changes take effect immediately in the
1401
+ * current worker: loads newly active plugins and removes uninstalled ones.
1402
+ */
1403
+ async syncMarketplacePlugins() {
1404
+ if (!this.config.marketplace) return;
1405
+ if (this.runtimeDeps.sandboxBypassed) {
1406
+ await this.syncMarketplacePluginsBypassed();
1407
+ return;
1408
+ }
1409
+ await this.syncSandboxedSourcePlugins("marketplace");
1410
+ }
1411
+ /**
1412
+ * Synchronize registry plugin runtime state with DB + storage.
1413
+ *
1414
+ * Mirrors {@link syncMarketplacePlugins} for plugins installed via the
1415
+ * experimental decentralized plugin registry. Called after install,
1416
+ * update, and uninstall handlers complete.
1417
+ */
1418
+ async syncRegistryPlugins() {
1419
+ if (!getRegistryConfigInput(this.config.registry)) return;
1420
+ await this.syncSandboxedSourcePlugins("registry");
1421
+ }
1422
+ /**
1423
+ * Internal: reconcile in-memory sandboxed-plugin state with the
1424
+ * `_plugin_state` table for the given source tier. Shared
1425
+ * implementation behind {@link syncMarketplacePlugins} and
1426
+ * {@link syncRegistryPlugins}.
1427
+ *
1428
+ * Each source tier has its own key set in `${source}PluginKeys` so a
1429
+ * sync for one tier doesn't invalidate the other.
1430
+ */
1431
+ async syncSandboxedSourcePlugins(source) {
1432
+ if (!this.storage) return;
1433
+ if (!sandboxRunner || !sandboxRunner.isAvailable()) return;
1434
+ const keySet = source === "marketplace" ? marketplacePluginKeys : registryPluginKeys;
1435
+ let pipelineChanged = false;
1436
+ try {
1437
+ const stateRepo = new PluginStateRepository(this.db);
1438
+ const states = source === "marketplace" ? await stateRepo.getMarketplacePlugins() : await stateRepo.getRegistryPlugins();
1439
+ const desired = /* @__PURE__ */ new Map();
1440
+ for (const state of states) {
1441
+ this.pluginStates.set(state.pluginId, state.status);
1442
+ if (state.status === "active") this.enabledPlugins.add(state.pluginId);
1443
+ else this.enabledPlugins.delete(state.pluginId);
1444
+ if (state.status !== "active") continue;
1445
+ const desiredVersion = source === "marketplace" ? state.marketplaceVersion ?? state.version : state.version;
1446
+ desired.set(state.pluginId, desiredVersion);
1447
+ }
1448
+ const keysToRemove = [];
1449
+ for (const key of keySet) {
1450
+ const [pluginId] = key.split(":");
1451
+ if (!pluginId) continue;
1452
+ const desiredVersion = desired.get(pluginId);
1453
+ if (desiredVersion && key === `${pluginId}:${desiredVersion}`) continue;
1454
+ keysToRemove.push(key);
1455
+ }
1456
+ for (const key of keysToRemove) {
1457
+ const [pluginId] = key.split(":");
1458
+ if (!pluginId) continue;
1459
+ if (!desired.get(pluginId)) {
1460
+ this.pluginStates.delete(pluginId);
1461
+ this.enabledPlugins.delete(pluginId);
1462
+ }
1463
+ const existing = sandboxedPluginCache.get(key);
1464
+ if (existing) try {
1465
+ await existing.terminate();
1466
+ } catch (error) {
1467
+ console.warn(`EmDash: Failed to terminate sandboxed plugin ${key}:`, error);
1468
+ }
1469
+ sandboxedPluginCache.delete(key);
1470
+ sandboxedManifestCache.delete(key);
1471
+ this.sandboxedPlugins.delete(key);
1472
+ this.removePluginFromLists(pluginId);
1473
+ keySet.delete(key);
1474
+ pipelineChanged = true;
1475
+ if (pluginId) {
1476
+ sandboxedRouteMetaCache.delete(pluginId);
1477
+ marketplaceManifestCache.delete(pluginId);
1478
+ }
1479
+ }
1480
+ for (const [pluginId, version] of desired) {
1481
+ const key = `${pluginId}:${version}`;
1482
+ if (sandboxedPluginCache.has(key)) {
1483
+ keySet.add(key);
1484
+ continue;
1485
+ }
1486
+ const bundle = await loadBundleFromR2(this.storage, pluginId, version, source);
1487
+ if (!bundle) {
1488
+ console.warn(`EmDash: ${source} plugin ${pluginId}@${version} not found in R2`);
1489
+ continue;
1490
+ }
1491
+ const loaded = await sandboxRunner.load(bundle.manifest, bundle.backendCode);
1492
+ sandboxedPluginCache.set(key, loaded);
1493
+ sandboxedManifestCache.set(key, bundle.manifest);
1494
+ this.sandboxedPlugins.set(key, loaded);
1495
+ this.allPipelinePlugins.push(createSandboxedPluginProxy(bundle.manifest, loaded));
1496
+ keySet.add(key);
1497
+ pipelineChanged = true;
1498
+ marketplaceManifestCache.set(pluginId, {
1499
+ id: bundle.manifest.id,
1500
+ version: bundle.manifest.version,
1501
+ admin: bundle.manifest.admin,
1502
+ mcp: bundle.manifest.mcp,
1503
+ storage: bundle.manifest.storage,
1504
+ allowedHosts: bundle.manifest.allowedHosts,
1505
+ capabilities: bundle.manifest.capabilities
1506
+ });
1507
+ if (bundle.manifest.routes.length > 0) {
1508
+ const routeMetaMap = /* @__PURE__ */ new Map();
1509
+ for (const entry of bundle.manifest.routes) {
1510
+ const normalized = normalizeManifestRoute$1(entry);
1511
+ routeMetaMap.set(normalized.name, buildRouteMeta(normalized));
1512
+ }
1513
+ sandboxedRouteMetaCache.set(pluginId, routeMetaMap);
1514
+ } else sandboxedRouteMetaCache.delete(pluginId);
1515
+ }
1516
+ if (pipelineChanged) await this.rebuildHookPipeline();
1517
+ } catch (error) {
1518
+ console.error(`EmDash: Failed to sync ${source} plugins:`, error);
1519
+ }
1520
+ }
1521
+ /**
1522
+ * Remove a plugin from the in-memory pipeline lists by ID.
1523
+ * Mutates allPipelinePlugins and configuredPlugins in place.
1524
+ */
1525
+ removePluginFromLists(pluginId) {
1526
+ const allIdx = this.allPipelinePlugins.findIndex((p) => p.id === pluginId);
1527
+ if (allIdx !== -1) this.allPipelinePlugins.splice(allIdx, 1);
1528
+ const configIdx = this.configuredPlugins.findIndex((p) => p.id === pluginId);
1529
+ if (configIdx !== -1) this.configuredPlugins.splice(configIdx, 1);
1530
+ }
1531
+ /**
1532
+ * Sync marketplace plugin metadata in sandbox: false bypass mode.
1533
+ *
1534
+ * In bypass mode the noop runner can't load plugins, but admin pages,
1535
+ * widgets, and route metadata still need to refresh in-process when an
1536
+ * admin installs/updates/uninstalls a marketplace plugin. Otherwise the
1537
+ * admin UI shows stale data until the server restarts.
1538
+ *
1539
+ * Hooks and routes still won't execute under bypass (matches the
1540
+ * cold-start bypass behavior in loadMarketplacePluginsBypassed).
1541
+ *
1542
+ * Known limitation: bypass plugins are loaded via `import(dataUrl)`,
1543
+ * which Node's ESM cache keys on the full URL. Updates create fresh
1544
+ * module objects, but old ones remain cached for the worker's lifetime.
1545
+ * In practice this is a few KB per update — only matters for sites with
1546
+ * very frequent marketplace updates running long-lived processes. The
1547
+ * fix would be vm.SourceTextModule for explicit lifecycle management.
1548
+ */
1549
+ async syncMarketplacePluginsBypassed() {
1550
+ if (!this.storage) return;
1551
+ try {
1552
+ const marketplaceStates = await new PluginStateRepository(this.db).getMarketplacePlugins();
1553
+ const desired = /* @__PURE__ */ new Map();
1554
+ for (const state of marketplaceStates) {
1555
+ this.pluginStates.set(state.pluginId, state.status);
1556
+ if (state.status === "active") this.enabledPlugins.add(state.pluginId);
1557
+ else this.enabledPlugins.delete(state.pluginId);
1558
+ if (state.status !== "active") continue;
1559
+ desired.set(state.pluginId, state.marketplaceVersion ?? state.version);
1560
+ }
1561
+ const toRemove = [];
1562
+ for (const pluginId of marketplaceManifestCache.keys()) if (!desired.has(pluginId)) toRemove.push(pluginId);
1563
+ for (const pluginId of toRemove) {
1564
+ const resolved = this.allPipelinePlugins.find((p) => p.id === pluginId);
1565
+ if (resolved) try {
1566
+ const deactivateHook = resolved.hooks?.["plugin:deactivate"];
1567
+ if (deactivateHook) {
1568
+ const handler = typeof deactivateHook === "function" ? deactivateHook : deactivateHook.handler;
1569
+ if (typeof handler === "function") await handler({ pluginId }, {});
1570
+ }
1571
+ } catch (err) {
1572
+ console.warn(`[emdash] plugin:deactivate hook failed for ${pluginId}:`, err);
1573
+ }
1574
+ marketplaceManifestCache.delete(pluginId);
1575
+ sandboxedRouteMetaCache.delete(pluginId);
1576
+ this.removePluginFromLists(pluginId);
1577
+ this.enabledPlugins.delete(pluginId);
1578
+ }
1579
+ const { adaptSandboxEntry } = await import("./plugins/adapt-sandbox-entry.mjs");
1580
+ const newPlugins = [];
1581
+ for (const [pluginId, version] of desired) {
1582
+ const bundle = await loadBundleFromR2(this.storage, pluginId, version);
1583
+ if (!bundle) {
1584
+ console.warn(`EmDash: Marketplace plugin ${pluginId}@${version} not found in R2`);
1585
+ continue;
1586
+ }
1587
+ marketplaceManifestCache.set(pluginId, {
1588
+ id: bundle.manifest.id,
1589
+ version: bundle.manifest.version,
1590
+ admin: bundle.manifest.admin,
1591
+ mcp: bundle.manifest.mcp,
1592
+ storage: bundle.manifest.storage,
1593
+ allowedHosts: bundle.manifest.allowedHosts,
1594
+ capabilities: bundle.manifest.capabilities
1595
+ });
1596
+ if (bundle.manifest.routes.length > 0) {
1597
+ const routeMetaMap = /* @__PURE__ */ new Map();
1598
+ for (const entry of bundle.manifest.routes) {
1599
+ const normalized = normalizeManifestRoute$1(entry);
1600
+ routeMetaMap.set(normalized.name, buildRouteMeta(normalized));
1601
+ }
1602
+ sandboxedRouteMetaCache.set(pluginId, routeMetaMap);
1603
+ } else sandboxedRouteMetaCache.delete(pluginId);
1604
+ const existing = this.allPipelinePlugins.find((p) => p.id === pluginId);
1605
+ if (existing && existing.version === bundle.manifest.version) continue;
1606
+ if (existing) this.removePluginFromLists(pluginId);
1607
+ try {
1608
+ const pluginModule = await import(`data:text/javascript;base64,${Buffer.from(bundle.backendCode).toString("base64")}`);
1609
+ const adapted = adaptSandboxEntry(pluginModule.default ?? pluginModule, {
1610
+ id: bundle.manifest.id,
1611
+ version: bundle.manifest.version,
1612
+ entrypoint: "",
1613
+ capabilities: bundle.manifest.capabilities ?? [],
1614
+ allowedHosts: bundle.manifest.allowedHosts ?? [],
1615
+ storage: bundle.manifest.storage ?? {},
1616
+ adminPages: bundle.manifest.admin?.pages,
1617
+ adminWidgets: bundle.manifest.admin?.widgets?.map((w) => ({
1618
+ id: w.id,
1619
+ title: w.title,
1620
+ size: w.size === "full" || w.size === "half" || w.size === "third" ? w.size : void 0
1621
+ })),
1622
+ settingsSchema: bundle.manifest.admin?.settingsSchema,
1623
+ editorPanels: bundle.manifest.admin?.editorPanels,
1624
+ editorActions: bundle.manifest.admin?.editorActions,
1625
+ mcp: bundle.manifest.mcp
1626
+ });
1627
+ newPlugins.push(adapted);
1628
+ this.allPipelinePlugins.push(adapted);
1629
+ this.configuredPlugins.push(adapted);
1630
+ this.enabledPlugins.add(adapted.id);
1631
+ } catch (error) {
1632
+ console.error(`EmDash: Failed to load marketplace plugin ${pluginId}@${version} in-process:`, error);
1633
+ }
1634
+ }
1635
+ if (toRemove.length > 0 || newPlugins.length > 0) await this.rebuildHookPipeline();
1636
+ } catch (error) {
1637
+ console.error("EmDash: Failed to sync marketplace plugins (bypass):", error);
1638
+ }
1639
+ }
1640
+ /**
1641
+ * Create and initialize the runtime
1642
+ */
1643
+ static async create(deps, timings) {
1644
+ const phase = async (name, desc, fn) => {
1645
+ if (!timings) return fn();
1646
+ const t0 = performance.now();
1647
+ try {
1648
+ return await fn();
1649
+ } finally {
1650
+ timings.push({
1651
+ name,
1652
+ dur: performance.now() - t0,
1653
+ desc
1654
+ });
1655
+ }
1656
+ };
1657
+ const db = await phase("rt.db", "DB init + migration policy", () => EmDashRuntime.getDatabase(deps));
1658
+ const resolveDb = () => {
1659
+ return getRequestContext()?.db ?? db;
1660
+ };
1661
+ await phase("rt.secrets", "Validate encryption key", () => validateEncryptionKeyAtStartup());
1662
+ const storage = EmDashRuntime.getStorage(deps);
1663
+ let pluginStates = /* @__PURE__ */ new Map();
1664
+ let pluginStatesRead = false;
1665
+ let installedPluginStates = [];
1666
+ let exclusiveHookSelections;
1667
+ const configuredLocales = virtualConfig?.i18n?.locales ?? getI18nConfig()?.locales ?? [];
1668
+ const localeCasingRepairVersion = getLocaleCasingRepairVersion(configuredLocales);
1669
+ let storedLocaleCasingRepairVersion;
1670
+ let siteInfo;
1671
+ let seedComplete = true;
1672
+ let setupDone = true;
1673
+ let seedCollectionsReadable = false;
1674
+ const reqCtx = getRequestContext();
1675
+ const ownsConfiguredDb = !!deps.config.database && !(reqCtx?.dbIsIsolated && reqCtx.db);
1676
+ let readDb = db;
1677
+ let readDbDisposable;
1678
+ const disposeReadDb = async () => {
1679
+ const disposable = readDbDisposable;
1680
+ readDbDisposable = void 0;
1681
+ if (!disposable) return;
1682
+ try {
1683
+ await disposable.destroy();
1684
+ } catch {}
1685
+ };
1686
+ if (ownsConfiguredDb && deps.createCoalescingDialect && deps.config.database) try {
1687
+ const dialect = deps.createCoalescingDialect(deps.config.database.config);
1688
+ if (dialect) {
1689
+ readDb = new Kysely({
1690
+ dialect,
1691
+ log: kyselyLogOption()
1692
+ });
1693
+ readDbDisposable = readDb;
1694
+ }
1695
+ } catch {
1696
+ readDb = db;
1697
+ }
1698
+ const optionsRepo = new OptionsRepository(readDb);
1699
+ let missingManualSchemaError;
1700
+ const captureMissingManualSchema = (error) => {
1701
+ if ((deps.migrationMode ?? "auto") === "manual" && isMissingTableError(error)) missingManualSchemaError ??= error;
1702
+ };
1703
+ const readSiteInfo = async () => {
1704
+ const siteOpts = await optionsRepo.getMany([
1705
+ "emdash:site_title",
1706
+ "site:url",
1707
+ "emdash:site_url",
1708
+ "emdash:locale",
1709
+ LOCALE_CASING_REPAIR_OPTION,
1710
+ AUTO_SEED_COMPLETE_OPTION,
1711
+ SETUP_COMPLETE_OPTION
1712
+ ]);
1713
+ const siteTitle = siteOpts.get("emdash:site_title");
1714
+ const siteUrl = resolveSiteOrigin(deps.config, siteOpts.get("site:url"), siteOpts.get("emdash:site_url"));
1715
+ const locale = siteOpts.get("emdash:locale");
1716
+ const repairVersion = siteOpts.get(LOCALE_CASING_REPAIR_OPTION);
1717
+ storedLocaleCasingRepairVersion = typeof repairVersion === "string" ? repairVersion : void 0;
1718
+ seedComplete = siteOpts.get(AUTO_SEED_COMPLETE_OPTION) === true;
1719
+ setupDone = siteOpts.get(SETUP_COMPLETE_OPTION) === true;
1720
+ return {
1721
+ siteName: deps.siteInfo?.name ?? (typeof siteTitle === "string" ? siteTitle : void 0),
1722
+ siteUrl: deps.siteInfo?.url ?? siteUrl,
1723
+ locale: deps.siteInfo?.locale ?? (typeof locale === "string" ? locale : void 0),
1724
+ trailingSlash: deps.siteInfo?.trailingSlash ?? virtualConfig?.trailingSlash
1725
+ };
1726
+ };
1727
+ const coldStartReads = [
1728
+ phase("rt.plugins", "Plugin states", async () => {
1729
+ try {
1730
+ const states = await readDb.selectFrom("_plugin_state").selectAll().execute();
1731
+ pluginStates = new Map(states.map((s) => [s.plugin_id, s.status]));
1732
+ pluginStatesRead = true;
1733
+ installedPluginStates = states.filter((s) => s.source === "marketplace" || s.source === "registry").map(rowToPluginState);
1734
+ } catch (error) {
1735
+ captureMissingManualSchema(error);
1736
+ }
1737
+ }),
1738
+ phase("rt.site", "Site info options", async () => {
1739
+ try {
1740
+ siteInfo = await readSiteInfo();
1741
+ } catch (error) {
1742
+ captureMissingManualSchema(error);
1743
+ }
1744
+ }),
1745
+ phase("rt.hookselections", "Exclusive hook selections", async () => {
1746
+ const keys = Array.from(new Set(["comment:moderate", ...deps.plugins.flatMap((plugin) => Object.entries(plugin.hooks).filter(([, hook]) => hook?.exclusive).map(([name]) => name))]), (name) => `${EXCLUSIVE_HOOK_KEY_PREFIX}${name}`);
1747
+ try {
1748
+ const stored = await optionsRepo.getMany(keys);
1749
+ exclusiveHookSelections = new Map(keys.map((key) => [key, stored.get(key)]));
1750
+ } catch (error) {
1751
+ captureMissingManualSchema(error);
1752
+ }
1753
+ })
1754
+ ];
1755
+ if (ownsConfiguredDb) coldStartReads.push(phase("rt.seedcheck", "Auto-seed gate", async () => {
1756
+ try {
1757
+ primeRegisteredCollections((await readDb.selectFrom("_emdash_collections").select("slug").execute()).map((row) => row.slug));
1758
+ seedCollectionsReadable = true;
1759
+ } catch (error) {
1760
+ captureMissingManualSchema(error);
1761
+ }
1762
+ }));
1763
+ await Promise.all(coldStartReads);
1764
+ if (missingManualSchemaError) {
1765
+ await disposeReadDb();
1766
+ throw missingManualSchemaError;
1767
+ }
1768
+ if (localeCasingRepairVersion && (configuredLocales.some((locale) => locale.includes("-") || locale !== locale.toLowerCase()) || storedLocaleCasingRepairVersion !== void 0) && storedLocaleCasingRepairVersion !== localeCasingRepairVersion) await phase("rt.locale", "Repair locale casing", async () => {
1769
+ await repairLocaleCasing(db, configuredLocales);
1770
+ await new OptionsRepository(db).set(LOCALE_CASING_REPAIR_OPTION, localeCasingRepairVersion);
1771
+ });
1772
+ if (seedCollectionsReadable && !seedComplete && !setupDone) {
1773
+ try {
1774
+ if ((await activateMediaUsageCapture(db, { writersDrained: true })).outcome !== "active") throw new Error("Fresh-site media usage activation did not complete");
1775
+ } catch (error) {
1776
+ await disposeReadDb();
1777
+ throw error;
1778
+ }
1779
+ const seedKey = deps.config.database?.entrypoint ?? "default";
1780
+ const seedHolder = getSeedHolder();
1781
+ try {
1782
+ await initWithLock(seedHolder.lock, () => seedHolder.done.has(seedKey) ? true : void 0, async () => {
1783
+ const { applySeed } = await import("./apply-T7ATgzCh.mjs").then((n) => n.r);
1784
+ const { loadSeed } = await import("./load-CdAG9wz-.mjs").then((n) => n.r);
1785
+ const { validateSeed } = await import("./validate-DxYhJQA-.mjs").then((n) => n.r);
1786
+ const seed = await loadSeed();
1787
+ if (!validateSeed(seed).valid) return false;
1788
+ const seedResult = await applySeed(db, seed, { onConflict: "skip" });
1789
+ await new OptionsRepository(db).set(AUTO_SEED_COMPLETE_OPTION, true);
1790
+ console.log("Auto-seeded default collections");
1791
+ if (seedResult.taxonomies.skipped > 0) console.warn(`[auto-seed] Kept ${seedResult.taxonomies.skipped} existing taxonomy definition(s) instead of the seed's. Edit them in the admin, or run \`emdash seed <file> --on-conflict update\` to replace them (this also overwrites other seeded records).`);
1792
+ seedHolder.done.add(seedKey);
1793
+ return true;
1794
+ }, {
1795
+ deadlineMs: DB_INIT_DEADLINE_MS,
1796
+ anchor: (promise) => after(() => promise)
1797
+ });
1798
+ try {
1799
+ siteInfo = await readSiteInfo();
1800
+ } catch {}
1801
+ } catch {}
1802
+ }
1803
+ await disposeReadDb();
1804
+ const enabledPlugins = /* @__PURE__ */ new Set();
1805
+ for (const plugin of deps.plugins) {
1806
+ const status = pluginStates.get(plugin.id);
1807
+ if (status === void 0 || status === "active") enabledPlugins.add(plugin.id);
1808
+ }
1809
+ const allPipelinePlugins = [...deps.plugins];
1810
+ const bypassedPluginsList = [];
1811
+ if (import.meta.env.DEV) try {
1812
+ const devConsolePlugin = definePlugin({
1813
+ id: DEV_CONSOLE_EMAIL_PLUGIN_ID,
1814
+ version: "0.0.0",
1815
+ capabilities: ["hooks.email-transport:register"],
1816
+ hooks: { "email:deliver": {
1817
+ exclusive: true,
1818
+ handler: devConsoleEmailDeliver
1819
+ } }
1820
+ });
1821
+ allPipelinePlugins.push(devConsolePlugin);
1822
+ enabledPlugins.add(devConsolePlugin.id);
1823
+ } catch (error) {
1824
+ console.warn("[email] Failed to register dev console email provider:", error);
1825
+ }
1826
+ try {
1827
+ const defaultModeratorPlugin = definePlugin({
1828
+ id: DEFAULT_COMMENT_MODERATOR_PLUGIN_ID,
1829
+ version: "0.0.0",
1830
+ capabilities: ["users:read"],
1831
+ hooks: { "comment:moderate": {
1832
+ exclusive: true,
1833
+ handler: defaultCommentModerate
1834
+ } }
1835
+ });
1836
+ allPipelinePlugins.push(defaultModeratorPlugin);
1837
+ enabledPlugins.add(defaultModeratorPlugin.id);
1838
+ } catch (error) {
1839
+ console.warn("[comments] Failed to register default moderator:", error);
1840
+ }
1841
+ if (deps.sandboxBypassed && deps.sandboxedPluginEntries.length > 0) {
1842
+ if (typeof navigator !== "undefined" && typeof navigator.userAgent === "string" && navigator.userAgent.includes("Cloudflare-Workers")) throw new Error("sandbox: false is not supported in Cloudflare Workers. Remove the sandbox: false option or use the Cloudflare sandbox runner.");
1843
+ console.info("EmDash: Sandbox disabled (sandbox: false). Sandboxed plugins will run in-process without isolation.");
1844
+ const bypassedPlugins = await EmDashRuntime.loadBypassedPlugins(deps.sandboxedPluginEntries);
1845
+ for (const plugin of bypassedPlugins) {
1846
+ allPipelinePlugins.push(plugin);
1847
+ bypassedPluginsList.push(plugin);
1848
+ const status = pluginStates.get(plugin.id);
1849
+ if (status === void 0 || status === "active") enabledPlugins.add(plugin.id);
1850
+ }
1851
+ }
1852
+ if (deps.sandboxBypassed && deps.config.marketplace && storage) {
1853
+ const marketplaceBypassed = await EmDashRuntime.loadMarketplacePluginsBypassed(storage, installedPluginStates);
1854
+ for (const plugin of marketplaceBypassed) {
1855
+ allPipelinePlugins.push(plugin);
1856
+ bypassedPluginsList.push(plugin);
1857
+ const status = pluginStates.get(plugin.id);
1858
+ if (status === void 0 || status === "active") enabledPlugins.add(plugin.id);
1859
+ }
1860
+ }
1861
+ const sandboxedPluginPool = await phase("rt.sandbox", "Sandboxed plugins", () => EmDashRuntime.loadSandboxedPlugins(deps, db, storage, siteInfo));
1862
+ for (const [key, plugin] of sandboxedPluginPool) {
1863
+ const pluginId = key.slice(0, key.lastIndexOf(":"));
1864
+ const status = pluginStates.get(pluginId);
1865
+ plugin.setActive?.(status === void 0 || status === "active");
1866
+ }
1867
+ const installedTierPhases = [];
1868
+ if (deps.config.marketplace && storage && !deps.sandboxBypassed) installedTierPhases.push(phase("rt.market", "Marketplace plugins", () => EmDashRuntime.loadInstalledSandboxedPlugins("marketplace", db, storage, deps, sandboxedPluginPool, installedPluginStates, siteInfo)));
1869
+ if (getRegistryConfigInput(deps.config.registry) && storage) installedTierPhases.push(phase("rt.registry", "Registry plugins", () => EmDashRuntime.loadInstalledSandboxedPlugins("registry", db, storage, deps, sandboxedPluginPool, installedPluginStates, siteInfo)));
1870
+ if (installedTierPhases.length > 0) await Promise.all(installedTierPhases);
1871
+ const configuredSandboxIds = new Set(deps.sandboxedPluginEntries.map((entry) => entry.id));
1872
+ const sandboxedPlugins = new Map([...sandboxedPluginPool].filter(([pluginKey]) => {
1873
+ const pluginId = pluginKey.slice(0, pluginKey.lastIndexOf(":"));
1874
+ return configuredSandboxIds.has(pluginId) || pluginStates.get(pluginId) === "active";
1875
+ }));
1876
+ for (const [pluginKey, instance] of sandboxedPlugins) {
1877
+ const manifest = sandboxedManifestCache.get(pluginKey);
1878
+ if (!manifest) continue;
1879
+ allPipelinePlugins.push(createSandboxedPluginProxy(manifest, instance));
1880
+ const status = pluginStates.get(manifest.id);
1881
+ if (status === void 0 || status === "active") enabledPlugins.add(manifest.id);
1882
+ }
1883
+ const runtimeRef = { current: null };
1884
+ const requireRuntime = () => {
1885
+ if (!runtimeRef.current) throw new Error("EmDash runtime is not ready");
1886
+ return runtimeRef.current;
1887
+ };
1888
+ const contentActions = {
1889
+ begin: (pluginId, invocationId, invalidateContentCache) => requireRuntime().beginPluginInvocation(pluginId, invocationId, invalidateContentCache),
1890
+ flush: (pluginId, invocationId, final) => requireRuntime().flushPluginAfterHooks(pluginId, invocationId, final),
1891
+ getVersioned: (pluginId, collection, id) => requireRuntime().pluginGetVersioned(pluginId, collection, id),
1892
+ publish: (pluginId, collection, id, options, invocationId, invalidateContentCache) => requireRuntime().pluginPublish(pluginId, collection, id, options, invocationId, invalidateContentCache),
1893
+ unpublish: (pluginId, collection, id, options, invocationId, invalidateContentCache) => requireRuntime().pluginUnpublish(pluginId, collection, id, options, invocationId, invalidateContentCache),
1894
+ schedule: (pluginId, collection, id, options, invocationId, invalidateContentCache) => requireRuntime().pluginSchedule(pluginId, collection, id, options, invocationId, invalidateContentCache),
1895
+ unschedule: (pluginId, collection, id, options, invocationId, invalidateContentCache) => requireRuntime().pluginUnschedule(pluginId, collection, id, options, invocationId, invalidateContentCache),
1896
+ getTrashedVersioned: (pluginId, collection, id) => requireRuntime().pluginGetTrashedVersioned(pluginId, collection, id),
1897
+ restore: (pluginId, collection, id, options, invocationId, invalidateContentCache) => requireRuntime().pluginRestore(pluginId, collection, id, options, invocationId, invalidateContentCache)
1898
+ };
1899
+ const pipelineFactoryOptions = {
1900
+ db,
1901
+ getDb: resolveDb,
1902
+ beforeContentWrite: () => assertSiteWriteAllowed(resolveDb()),
1903
+ contentActions,
1904
+ now: deps.now,
1905
+ storage: storage ?? void 0,
1906
+ siteInfo,
1907
+ commentModerate: (pluginId, id, status, expectedStatus) => {
1908
+ const current = runtimeRef.current;
1909
+ if (!current) throw new Error("Comment moderation is unavailable during runtime startup");
1910
+ return current.handlePluginCommentModerate(pluginId, id, status, expectedStatus);
1911
+ }
1912
+ };
1913
+ const pipeline = createHookPipeline(allPipelinePlugins.filter((p) => enabledPlugins.has(p.id)), pipelineFactoryOptions);
1914
+ const mediaProviders = /* @__PURE__ */ new Map();
1915
+ const mediaProviderEntries = deps.mediaProviderEntries ?? [];
1916
+ const providerContext = {
1917
+ db,
1918
+ storage,
1919
+ getDb: resolveDb
1920
+ };
1921
+ for (const entry of mediaProviderEntries) try {
1922
+ const provider = entry.createProvider(providerContext);
1923
+ mediaProviders.set(entry.id, provider);
1924
+ } catch (error) {
1925
+ console.warn(`Failed to initialize media provider "${entry.id}":`, error);
1926
+ }
1927
+ await phase("rt.hooks", "Exclusive hook resolution", () => EmDashRuntime.resolveExclusiveHooks(pipeline, db, deps, exclusiveHookSelections));
1928
+ const emailPipeline = new EmailPipeline(pipeline);
1929
+ if (sandboxRunner) sandboxRunner.setEmailSend((message, pluginId) => emailPipeline.send(message, pluginId));
1930
+ const pipelineRef = { current: pipeline };
1931
+ const invokeCronHook = async (pluginId, event) => {
1932
+ const result = await pipelineRef.current.invokeCronHook(pluginId, event);
1933
+ if (!result.success && result.error) throw result.error;
1934
+ };
1935
+ pipeline.setContextFactory({ emailPipeline });
1936
+ let cronExecutor = null;
1937
+ let cronScheduler = null;
1938
+ await phase("rt.cron", "Cron init (recovery deferred post-response)", async () => {
1939
+ try {
1940
+ cronExecutor = new CronExecutor(resolveDb, invokeCronHook, deps.now);
1941
+ pipeline.setContextFactory({ cronReschedule: () => cronScheduler?.reschedule() });
1942
+ const executorForRecovery = cronExecutor;
1943
+ after(async () => {
1944
+ try {
1945
+ const recovered = await executorForRecovery.recoverStaleLocks();
1946
+ if (recovered > 0) console.log(`[cron] Recovered ${recovered} stale task lock(s)`);
1947
+ } catch (error) {
1948
+ console.error("[cron] Failed to recover stale task locks:", error);
1949
+ }
1950
+ });
1951
+ if (deps.createScheduler) {
1952
+ const scheduler = deps.createScheduler(cronExecutor);
1953
+ cronScheduler = scheduler;
1954
+ scheduler.setSystemCleanup(async () => {
1955
+ const runtime = runtimeRef.current;
1956
+ if (!runtime) return;
1957
+ const currentTime = deps.now?.() ?? /* @__PURE__ */ new Date();
1958
+ try {
1959
+ await runtime.runScheduledMaintenanceTick({}, currentTime);
1960
+ } catch (error) {
1961
+ console.error("[scheduled] Maintenance tick failed:", error);
1962
+ }
1963
+ });
1964
+ scheduler.start();
1965
+ }
1966
+ } catch (error) {
1967
+ console.warn("[cron] Failed to initialize cron system:", error);
1968
+ }
1969
+ });
1970
+ sandboxRunner?.setCronReschedule?.(() => cronScheduler?.reschedule());
1971
+ const runtime = new EmDashRuntime({
1972
+ db,
1973
+ storage,
1974
+ configuredPlugins: [...deps.plugins, ...bypassedPluginsList],
1975
+ sandboxedPlugins,
1976
+ sandboxedPluginEntries: deps.sandboxedPluginEntries,
1977
+ hooks: pipeline,
1978
+ enabledPlugins,
1979
+ pluginStates,
1980
+ config: deps.config,
1981
+ mediaProviders,
1982
+ mediaProviderEntries,
1983
+ cronExecutor,
1984
+ cronScheduler,
1985
+ emailPipeline,
1986
+ allPipelinePlugins,
1987
+ pipelineFactoryOptions,
1988
+ runtimeDeps: deps,
1989
+ pipelineRef
1990
+ });
1991
+ const contentCreate = async (_pluginId, collection, input, options) => {
1992
+ const { seo, ...data } = input;
1993
+ const result = await runtime.handleContentCreate(collection, {
1994
+ data,
1995
+ seo,
1996
+ locale: resolveContentCreateLocale(options?.locale),
1997
+ translationOf: options?.translationOf
1998
+ }, {
1999
+ skipSaveHooks: options?.sandboxOrigin ? options.originHook !== void 0 : getActiveContentSaveHookName() != null,
2000
+ excludeAfterSavePluginId: _pluginId
2001
+ });
2002
+ if (!result.success) throw Object.assign(new Error(result.error.message), {
2003
+ name: result.error.code,
2004
+ code: result.error.code
2005
+ });
2006
+ return result.data.item;
2007
+ };
2008
+ runtime.pipelineFactoryOptions.contentCreate = contentCreate;
2009
+ pipeline.setContextFactory({ contentCreate });
2010
+ sandboxRunner?.setContentCreate?.(contentCreate);
2011
+ runtimeRef.current = runtime;
2012
+ sandboxRunner?.setCommentModerate?.((pluginId, id, status, expectedStatus) => runtime.handlePluginCommentModerate(pluginId, id, status, expectedStatus));
2013
+ sandboxRunner?.setContentActions?.(contentActions);
2014
+ if (ownsConfiguredDb && pluginStatesRead) await phase("rt.lifecycle", "Config plugin install/activate", () => runtime.installUnrecordedConfigPlugins(deps.plugins, new Map(pluginStates)));
2015
+ return runtime;
2016
+ }
2017
+ /**
2018
+ * Get a media provider by ID
2019
+ */
2020
+ getMediaProvider(providerId) {
2021
+ return this.mediaProviders.get(providerId);
2022
+ }
2023
+ /**
2024
+ * Get all media provider entries (for admin UI)
2025
+ */
2026
+ getMediaProviderList() {
2027
+ return this.mediaProviderEntries.map((e) => ({
2028
+ id: e.id,
2029
+ name: e.name,
2030
+ icon: e.icon,
2031
+ capabilities: e.capabilities
2032
+ }));
2033
+ }
2034
+ /**
2035
+ * Get or create database instance
2036
+ */
2037
+ static async getDatabase(deps) {
2038
+ const ctx = getRequestContext();
2039
+ if (ctx?.dbIsIsolated && ctx.db) return ctx.db;
2040
+ const dbConfig = deps.config.database;
2041
+ if (!dbConfig) try {
2042
+ return await getDb();
2043
+ } catch {
2044
+ throw new Error("EmDash database not configured. Either configure database in astro.config.mjs or use emdashLoader in live.config.ts");
2045
+ }
2046
+ const cacheKey = dbConfig.entrypoint;
2047
+ const holder = getDbHolder();
2048
+ const throwIfBackingOff = () => {
2049
+ const failure = holder.failures.get(cacheKey);
2050
+ if (!failure) return;
2051
+ if (Date.now() - failure.at < DB_INIT_FAILURE_BACKOFF_MS) throw new Error(`Database initialization is backing off after a recent migration failure: ${failure.message}`);
2052
+ holder.failures.delete(cacheKey);
2053
+ };
2054
+ throwIfBackingOff();
2055
+ return initWithLock(holder.lock, () => holder.cache.get(cacheKey), async (isCurrentClaim) => {
2056
+ throwIfBackingOff();
2057
+ const db = new Kysely({
2058
+ dialect: deps.createDialect(dbConfig.config),
2059
+ log: kyselyLogOption()
2060
+ });
2061
+ try {
2062
+ await enforceRuntimeMigrationPolicy(db, deps.migrationMode ?? "auto");
2063
+ } catch (error) {
2064
+ if (error instanceof MigrationFailedError || error instanceof MigrationLockHeldError) holder.failures.set(cacheKey, {
2065
+ at: Date.now(),
2066
+ message: error instanceof Error ? error.message : String(error)
2067
+ });
2068
+ await db.destroy().catch(() => {});
2069
+ throw error;
2070
+ }
2071
+ holder.failures.delete(cacheKey);
2072
+ if (isCurrentClaim()) holder.cache.set(cacheKey, db);
2073
+ return db;
2074
+ }, {
2075
+ deadlineMs: DB_INIT_DEADLINE_MS,
2076
+ anchor: (promise) => after(() => promise)
2077
+ });
2078
+ }
2079
+ /**
2080
+ * Get or create storage instance
2081
+ */
2082
+ static getStorage(deps) {
2083
+ const storageConfig = deps.config.storage;
2084
+ if (!storageConfig || !deps.createStorage) return null;
2085
+ const cacheKey = storageConfig.entrypoint;
2086
+ const cached = storageCache.get(cacheKey);
2087
+ if (cached) return cached;
2088
+ const storage = deps.createStorage(storageConfig.config);
2089
+ storageCache.set(cacheKey, storage);
2090
+ return storage;
2091
+ }
2092
+ /**
2093
+ * Load sandboxed plugin entries as trusted in-process plugins.
2094
+ * Used by the sandbox: false debugging escape hatch.
2095
+ *
2096
+ * Imports each plugin's bundled ESM code via a data URL, adapts it
2097
+ * with adaptSandboxEntry, and returns ResolvedPlugin objects ready
2098
+ * to be merged into the pipeline plugin list.
2099
+ */
2100
+ static async loadBypassedPlugins(entries) {
2101
+ const { adaptSandboxEntry } = await import("./plugins/adapt-sandbox-entry.mjs");
2102
+ const plugins = [];
2103
+ for (const entry of entries) try {
2104
+ const pluginModule = await import(`data:text/javascript;base64,${Buffer.from(entry.code).toString("base64")}`);
2105
+ const pluginDef = pluginModule.default ?? pluginModule;
2106
+ const adminPages = entry.adminPages?.map((p) => ({
2107
+ path: p.path,
2108
+ label: p.label ?? p.path,
2109
+ icon: p.icon,
2110
+ group: p.group
2111
+ }));
2112
+ const adminWidgets = entry.adminWidgets?.map((w) => {
2113
+ const size = w.size === "full" || w.size === "half" || w.size === "third" ? w.size : void 0;
2114
+ return {
2115
+ id: w.id,
2116
+ title: w.title,
2117
+ size
2118
+ };
2119
+ });
2120
+ const resolved = adaptSandboxEntry(pluginDef, {
2121
+ id: entry.id,
2122
+ version: entry.version,
2123
+ entrypoint: "",
2124
+ capabilities: entry.capabilities,
2125
+ allowedHosts: entry.allowedHosts,
2126
+ storage: entry.storage,
2127
+ adminPages,
2128
+ adminWidgets,
2129
+ settingsSchema: entry.settingsSchema,
2130
+ portableTextBlocks: entry.portableTextBlocks,
2131
+ fieldWidgets: entry.fieldWidgets,
2132
+ editorPanels: entry.editorPanels,
2133
+ editorActions: entry.editorActions,
2134
+ mcp: entry.mcp
2135
+ });
2136
+ plugins.push(resolved);
2137
+ console.log(`EmDash: Loaded plugin ${entry.id}:${entry.version} in-process (sandbox bypassed)`);
2138
+ } catch (error) {
2139
+ console.error(`EmDash: Failed to load sandboxed plugin ${entry.id} in-process:`, error);
2140
+ }
2141
+ return plugins;
2142
+ }
2143
+ /**
2144
+ * Load sandboxed plugins using SandboxRunner
2145
+ */
2146
+ static async loadSandboxedPlugins(deps, db, mediaStorage, siteInfo) {
2147
+ if (!deps.sandboxEnabled) return sandboxedPluginCache;
2148
+ if (!sandboxRunner && deps.createSandboxRunner) sandboxRunner = deps.createSandboxRunner(createSandboxRunnerOptions({
2149
+ db,
2150
+ beforeContentWrite: () => assertSiteWriteAllowed(db),
2151
+ taxonomyWrite: createTaxonomyAccessWithWrite(db),
2152
+ now: deps.now,
2153
+ mediaStorage: mediaStorage ? {
2154
+ upload: (opts) => mediaStorage.upload({
2155
+ key: opts.key,
2156
+ body: opts.body,
2157
+ contentType: opts.contentType
2158
+ }),
2159
+ download: (key) => mediaStorage.download(key),
2160
+ delete: (key) => mediaStorage.delete(key)
2161
+ } : void 0
2162
+ }, siteInfo));
2163
+ if (!sandboxRunner) return sandboxedPluginCache;
2164
+ if (!sandboxRunner.isAvailable()) {
2165
+ const reason = sandboxRunner.unavailableReason?.();
2166
+ console.warn(reason ? `EmDash: Plugin sandbox is configured but not available on this platform: ${reason}. Sandboxed plugins will not be loaded.` : "EmDash: Plugin sandbox is configured but not available on this platform. Sandboxed plugins will not be loaded. If using @emdash-cms/sandbox-workerd/sandbox, ensure workerd is installed.");
2167
+ return sandboxedPluginCache;
2168
+ }
2169
+ if (deps.sandboxedPluginEntries.length === 0) return sandboxedPluginCache;
2170
+ if (deps.sandboxBypassed) return sandboxedPluginCache;
2171
+ for (const entry of deps.sandboxedPluginEntries) {
2172
+ const pluginKey = `${entry.id}:${entry.version}`;
2173
+ if (sandboxedPluginCache.has(pluginKey)) continue;
2174
+ try {
2175
+ const adminPages = entry.adminPages?.map((page) => ({
2176
+ path: page.path,
2177
+ label: page.label ?? page.path,
2178
+ icon: page.icon,
2179
+ group: page.group
2180
+ }));
2181
+ const adminWidgets = entry.adminWidgets?.map((widget) => ({
2182
+ id: widget.id,
2183
+ title: widget.title,
2184
+ size: widget.size === "full" || widget.size === "half" || widget.size === "third" ? widget.size : void 0
2185
+ }));
2186
+ warnDeprecatedPluginCapabilities(entry.id, entry.capabilities ?? []);
2187
+ const capabilities = normalizePluginCapabilities(entry.capabilities ?? []);
2188
+ const manifest = {
2189
+ id: entry.id,
2190
+ version: entry.version,
2191
+ capabilities,
2192
+ allowedHosts: entry.allowedHosts ?? [],
2193
+ storage: entry.storage ?? {},
2194
+ hooks: entry.hooks ?? [...HOOK_NAMES],
2195
+ routes: entry.routes ?? [],
2196
+ admin: {
2197
+ pages: adminPages,
2198
+ widgets: adminWidgets,
2199
+ settingsSchema: entry.settingsSchema,
2200
+ portableTextBlocks: entry.portableTextBlocks,
2201
+ fieldWidgets: entry.fieldWidgets,
2202
+ editorPanels: entry.editorPanels,
2203
+ editorActions: entry.editorActions
2204
+ },
2205
+ mcp: entry.mcp
2206
+ };
2207
+ const plugin = await sandboxRunner.load(manifest, entry.code);
2208
+ sandboxedPluginCache.set(pluginKey, plugin);
2209
+ sandboxedManifestCache.set(pluginKey, manifest);
2210
+ console.log(`EmDash: Loaded sandboxed plugin ${pluginKey} with capabilities: [${manifest.capabilities.join(", ")}]`);
2211
+ if (manifest.routes.length > 0) {
2212
+ const routeMetaMap = /* @__PURE__ */ new Map();
2213
+ for (const routeEntry of manifest.routes) {
2214
+ const normalized = normalizeManifestRoute$1(routeEntry);
2215
+ routeMetaMap.set(normalized.name, buildRouteMeta(normalized));
2216
+ }
2217
+ sandboxedRouteMetaCache.set(entry.id, routeMetaMap);
2218
+ } else sandboxedRouteMetaCache.delete(entry.id);
2219
+ } catch (error) {
2220
+ console.error(`EmDash: Failed to load sandboxed plugin ${entry.id}:`, error);
2221
+ }
2222
+ }
2223
+ return sandboxedPluginCache;
2224
+ }
2225
+ /**
2226
+ * Cold-start load of all active sandboxed plugins for one install
2227
+ * tier (marketplace or registry) from site-local R2.
2228
+ *
2229
+ * Mirrors {@link syncSandboxedSourcePlugins} but runs once at runtime
2230
+ * creation, before request traffic arrives; the sync method runs on
2231
+ * demand after install / update / uninstall handlers. `states` are the
2232
+ * `_plugin_state` rows read in the cold-start batch.
2233
+ */
2234
+ static async loadInstalledSandboxedPlugins(source, db, storage, deps, cache, states, siteInfo) {
2235
+ if (!sandboxRunner && deps.createSandboxRunner) sandboxRunner = deps.createSandboxRunner(createSandboxRunnerOptions({
2236
+ db,
2237
+ beforeContentWrite: () => assertSiteWriteAllowed(db),
2238
+ taxonomyWrite: createTaxonomyAccessWithWrite(db),
2239
+ now: deps.now,
2240
+ mediaStorage: {
2241
+ upload: (opts) => storage.upload({
2242
+ key: opts.key,
2243
+ body: opts.body,
2244
+ contentType: opts.contentType
2245
+ }),
2246
+ download: (key) => storage.download(key),
2247
+ delete: (key) => storage.delete(key)
2248
+ }
2249
+ }, siteInfo));
2250
+ if (deps.sandboxBypassed) return;
2251
+ if (!sandboxRunner || !sandboxRunner.isAvailable()) return;
2252
+ const keySet = source === "marketplace" ? marketplacePluginKeys : registryPluginKeys;
2253
+ for (const plugin of states.filter((state) => state.source === source)) {
2254
+ if (plugin.status !== "active") continue;
2255
+ const version = source === "marketplace" ? plugin.marketplaceVersion ?? plugin.version : plugin.version;
2256
+ const pluginKey = `${plugin.pluginId}:${version}`;
2257
+ if (cache.has(pluginKey)) continue;
2258
+ try {
2259
+ const bundle = await loadBundleFromR2(storage, plugin.pluginId, version, source);
2260
+ if (!bundle) {
2261
+ console.warn(`EmDash: ${source} plugin ${plugin.pluginId}@${version} not found in R2`);
2262
+ continue;
2263
+ }
2264
+ const loaded = await sandboxRunner.load(bundle.manifest, bundle.backendCode);
2265
+ cache.set(pluginKey, loaded);
2266
+ sandboxedManifestCache.set(pluginKey, bundle.manifest);
2267
+ keySet.add(pluginKey);
2268
+ marketplaceManifestCache.set(plugin.pluginId, {
2269
+ id: bundle.manifest.id,
2270
+ version: bundle.manifest.version,
2271
+ admin: bundle.manifest.admin,
2272
+ mcp: bundle.manifest.mcp,
2273
+ storage: bundle.manifest.storage,
2274
+ allowedHosts: bundle.manifest.allowedHosts,
2275
+ capabilities: bundle.manifest.capabilities
2276
+ });
2277
+ if (bundle.manifest.routes.length > 0) {
2278
+ const routeMeta = /* @__PURE__ */ new Map();
2279
+ for (const entry of bundle.manifest.routes) {
2280
+ const normalized = normalizeManifestRoute$1(entry);
2281
+ routeMeta.set(normalized.name, buildRouteMeta(normalized));
2282
+ }
2283
+ sandboxedRouteMetaCache.set(plugin.pluginId, routeMeta);
2284
+ }
2285
+ console.log(`EmDash: Loaded ${source} plugin ${pluginKey} with capabilities: [${bundle.manifest.capabilities.join(", ")}]`);
2286
+ } catch (error) {
2287
+ console.error(`EmDash: Failed to load ${source} plugin ${plugin.pluginId}:`, error);
2288
+ }
2289
+ }
2290
+ }
2291
+ /**
2292
+ * Cold-start: load marketplace plugins in bypass mode (sandbox: false).
2293
+ *
2294
+ * Each active marketplace bundle is read, evaluated via data URL, adapted
2295
+ * with adaptSandboxEntry, and returned as a ResolvedPlugin. The caller is
2296
+ * responsible for merging these into allPipelinePlugins / configuredPlugins
2297
+ * BEFORE the hook pipeline is created, so hooks and routes register in
2298
+ * the trusted pipeline.
2299
+ *
2300
+ * Also caches manifest and route metadata so admin UI / getManifest() work.
2301
+ *
2302
+ * Returns ResolvedPlugins to be merged into the pipeline.
2303
+ */
2304
+ static async loadMarketplacePluginsBypassed(storage, states) {
2305
+ const resolved = [];
2306
+ const marketplacePlugins = states.filter((state) => state.source === "marketplace");
2307
+ if (marketplacePlugins.length === 0) return resolved;
2308
+ console.info("EmDash: Sandbox disabled (sandbox: false). Marketplace plugins will run in-process without isolation.");
2309
+ const { adaptSandboxEntry } = await import("./plugins/adapt-sandbox-entry.mjs");
2310
+ for (const plugin of marketplacePlugins) {
2311
+ if (plugin.status !== "active") continue;
2312
+ const version = plugin.marketplaceVersion ?? plugin.version;
2313
+ try {
2314
+ const bundle = await loadBundleFromR2(storage, plugin.pluginId, version);
2315
+ if (!bundle) {
2316
+ console.warn(`EmDash: Marketplace plugin ${plugin.pluginId}@${version} not found in R2`);
2317
+ continue;
2318
+ }
2319
+ marketplaceManifestCache.set(plugin.pluginId, {
2320
+ id: bundle.manifest.id,
2321
+ version: bundle.manifest.version,
2322
+ admin: bundle.manifest.admin,
2323
+ mcp: bundle.manifest.mcp,
2324
+ storage: bundle.manifest.storage,
2325
+ allowedHosts: bundle.manifest.allowedHosts,
2326
+ capabilities: bundle.manifest.capabilities
2327
+ });
2328
+ if (bundle.manifest.routes.length > 0) {
2329
+ const routeMeta = /* @__PURE__ */ new Map();
2330
+ for (const entry of bundle.manifest.routes) {
2331
+ const normalized = normalizeManifestRoute$1(entry);
2332
+ routeMeta.set(normalized.name, buildRouteMeta(normalized));
2333
+ }
2334
+ sandboxedRouteMetaCache.set(plugin.pluginId, routeMeta);
2335
+ }
2336
+ const pluginModule = await import(`data:text/javascript;base64,${Buffer.from(bundle.backendCode).toString("base64")}`);
2337
+ const adapted = adaptSandboxEntry(pluginModule.default ?? pluginModule, {
2338
+ id: bundle.manifest.id,
2339
+ version: bundle.manifest.version,
2340
+ entrypoint: "",
2341
+ capabilities: bundle.manifest.capabilities ?? [],
2342
+ allowedHosts: bundle.manifest.allowedHosts ?? [],
2343
+ storage: bundle.manifest.storage ?? {},
2344
+ adminPages: bundle.manifest.admin?.pages,
2345
+ adminWidgets: bundle.manifest.admin?.widgets?.map((w) => ({
2346
+ id: w.id,
2347
+ title: w.title,
2348
+ size: w.size === "full" || w.size === "half" || w.size === "third" ? w.size : void 0
2349
+ })),
2350
+ settingsSchema: bundle.manifest.admin?.settingsSchema,
2351
+ editorPanels: bundle.manifest.admin?.editorPanels,
2352
+ editorActions: bundle.manifest.admin?.editorActions,
2353
+ mcp: bundle.manifest.mcp
2354
+ });
2355
+ resolved.push(adapted);
2356
+ console.log(`EmDash: Loaded marketplace plugin ${plugin.pluginId}@${version} in-process (sandbox bypassed)`);
2357
+ } catch (error) {
2358
+ console.error(`EmDash: Failed to load marketplace plugin ${plugin.pluginId} in-process:`, error);
2359
+ }
2360
+ }
2361
+ return resolved;
2362
+ }
2363
+ /**
2364
+ * Resolve exclusive hook selections on startup.
2365
+ *
2366
+ * Delegates to the shared resolveExclusiveHooks() in hooks.ts.
2367
+ * The runtime version considers all pipeline providers as "active" since
2368
+ * the pipeline was already built from only active/enabled plugins.
2369
+ */
2370
+ static async resolveExclusiveHooks(pipeline, db, deps, storedSelections) {
2371
+ if (pipeline.getRegisteredExclusiveHooks().length === 0) return;
2372
+ let optionsRepo;
2373
+ try {
2374
+ optionsRepo = new OptionsRepository(db);
2375
+ } catch {
2376
+ return;
2377
+ }
2378
+ const preferredHints = /* @__PURE__ */ new Map();
2379
+ for (const entry of deps.sandboxedPluginEntries) if (entry.preferred && entry.preferred.length > 0) preferredHints.set(entry.id, entry.preferred);
2380
+ await resolveExclusiveHooks({
2381
+ pipeline,
2382
+ isActive: () => true,
2383
+ getOption: (key) => optionsRepo.get(key),
2384
+ getOptions: async (keys) => {
2385
+ const unread = keys.filter((key) => !storedSelections?.has(key));
2386
+ const selections = unread.length > 0 ? await optionsRepo.getMany(unread) : /* @__PURE__ */ new Map();
2387
+ for (const key of keys) {
2388
+ const value = storedSelections?.get(key);
2389
+ if (value !== void 0) selections.set(key, value);
2390
+ }
2391
+ return selections;
2392
+ },
2393
+ setOption: (key, value) => optionsRepo.set(key, value),
2394
+ deleteOption: async (key) => {
2395
+ await optionsRepo.delete(key);
2396
+ },
2397
+ preferredHints,
2398
+ fallbackProviders: new Set([DEFAULT_COMMENT_MODERATOR_PLUGIN_ID])
2399
+ });
2400
+ }
2401
+ /**
2402
+ * Build the admin manifest from the live database.
2403
+ *
2404
+ * Used by the admin UI (sidebar collections, content editor field
2405
+ * dispatch, manifest endpoint) and by WordPress import — it's never
2406
+ * read on a public request, so this isn't on any anonymous hot path.
2407
+ *
2408
+ * No cross-request cache. The previous worker-isolate cache produced
2409
+ * a class of cross-isolate staleness bugs (#776, #873, #876, #877)
2410
+ * because Cloudflare Workers keeps multiple warm isolates per region
2411
+ * and there's no fan-out primitive to invalidate them in step. The
2412
+ * cache existed to amortize an N+1 schema query pattern; now that
2413
+ * `listCollectionsWithFields()` does the same work in two queries,
2414
+ * the rebuild is fast enough to pay on every admin request.
2415
+ *
2416
+ * Within a single request, `requestCached` deduplicates concurrent
2417
+ * callers (the manifest endpoint and an admin SSR template, say).
2418
+ */
2419
+ getManifest() {
2420
+ return requestCached("emdash:manifest", () => this._buildManifest());
2421
+ }
2422
+ /**
2423
+ * Build the manifest from the database.
2424
+ *
2425
+ * Constant query shapes via `listCollectionsWithFields()` — one query
2426
+ * for collections, one batched query for fields (chunked at
2427
+ * `SQL_BATCH_SIZE` collection IDs to stay under D1's bound-parameter
2428
+ * limit). Typical sites stay well under the chunk threshold, so this
2429
+ * is two queries in practice; never N+1.
2430
+ */
2431
+ async _buildManifest() {
2432
+ const manifestCollections = await buildManifestCollections({}, this.db);
2433
+ const manifestPlugins = {};
2434
+ for (const plugin of this.configuredPlugins) {
2435
+ const status = this.pluginStates.get(plugin.id);
2436
+ const enabled = status === void 0 || status === "active";
2437
+ const hasAdminEntry = !!plugin.admin?.entry;
2438
+ const hasAdminPages = (plugin.admin?.pages?.length ?? 0) > 0;
2439
+ const hasWidgets = (plugin.admin?.widgets?.length ?? 0) > 0;
2440
+ const hasEditorExtensions = (plugin.admin?.editorPanels?.length ?? 0) > 0 || (plugin.admin?.editorActions?.length ?? 0) > 0;
2441
+ let adminMode = "none";
2442
+ if (hasAdminEntry) adminMode = "react";
2443
+ else if (hasAdminPages || hasWidgets || hasEditorExtensions) adminMode = "blocks";
2444
+ manifestPlugins[plugin.id] = {
2445
+ version: plugin.version,
2446
+ enabled,
2447
+ adminMode,
2448
+ adminPages: plugin.admin?.pages ?? [],
2449
+ dashboardWidgets: plugin.admin?.widgets ?? [],
2450
+ portableTextBlocks: plugin.admin?.portableTextBlocks,
2451
+ fieldWidgets: plugin.admin?.fieldWidgets,
2452
+ editorPanels: plugin.admin?.editorPanels,
2453
+ editorActions: plugin.admin?.editorActions
2454
+ };
2455
+ }
2456
+ for (const entry of this.sandboxedPluginEntries) {
2457
+ const status = this.pluginStates.get(entry.id);
2458
+ const enabled = status === void 0 || status === "active";
2459
+ const hasAdminPages = (entry.adminPages?.length ?? 0) > 0;
2460
+ const hasWidgets = (entry.adminWidgets?.length ?? 0) > 0;
2461
+ const hasEditorExtensions = (entry.editorPanels?.length ?? 0) > 0 || (entry.editorActions?.length ?? 0) > 0;
2462
+ manifestPlugins[entry.id] = {
2463
+ version: entry.version,
2464
+ enabled,
2465
+ sandboxed: true,
2466
+ adminMode: hasAdminPages || hasWidgets || hasEditorExtensions ? "blocks" : "none",
2467
+ adminPages: entry.adminPages ?? [],
2468
+ dashboardWidgets: entry.adminWidgets ?? [],
2469
+ portableTextBlocks: entry.portableTextBlocks,
2470
+ fieldWidgets: entry.fieldWidgets,
2471
+ editorPanels: entry.editorPanels,
2472
+ editorActions: entry.editorActions
2473
+ };
2474
+ }
2475
+ for (const [pluginId, meta] of marketplaceManifestCache) {
2476
+ if (manifestPlugins[pluginId]) continue;
2477
+ const enabled = this.pluginStates.get(pluginId) === "active";
2478
+ const pages = meta.admin?.pages;
2479
+ const widgets = meta.admin?.widgets;
2480
+ const hasAdminPages = (pages?.length ?? 0) > 0;
2481
+ const hasWidgets = (widgets?.length ?? 0) > 0;
2482
+ const hasEditorExtensions = (meta.admin?.editorPanels?.length ?? 0) > 0 || (meta.admin?.editorActions?.length ?? 0) > 0;
2483
+ manifestPlugins[pluginId] = {
2484
+ version: meta.version,
2485
+ enabled,
2486
+ sandboxed: true,
2487
+ adminMode: hasAdminPages || hasWidgets || hasEditorExtensions ? "blocks" : "none",
2488
+ adminPages: pages ?? [],
2489
+ dashboardWidgets: widgets ?? [],
2490
+ editorPanels: meta.admin?.editorPanels,
2491
+ editorActions: meta.admin?.editorActions
2492
+ };
2493
+ }
2494
+ let manifestTaxonomies = [];
2495
+ let taxonomyDefinitionLocales = [];
2496
+ try {
2497
+ const rows = await selectTaxonomyDefs(this.db).orderBy("d.name").execute();
2498
+ taxonomyDefinitionLocales = rows.map((row) => row.locale);
2499
+ manifestTaxonomies = rows.map((row) => ({
2500
+ id: row.id,
2501
+ name: row.name,
2502
+ label: row.label,
2503
+ labelSingular: row.label_singular ?? void 0,
2504
+ hierarchical: row.hierarchical === 1,
2505
+ collections: parseStringArray(row.collections).toSorted(),
2506
+ locale: row.locale,
2507
+ translationGroup: row.translation_group ?? row.id
2508
+ }));
2509
+ } catch (error) {
2510
+ console.debug("EmDash: Could not load taxonomy definitions:", error);
2511
+ }
2512
+ try {
2513
+ const configuredLocales = virtualConfig?.i18n?.locales ?? getI18nConfig()?.locales ?? [];
2514
+ await warnAboutUnconfiguredTaxonomyLocales(this.db, configuredLocales, taxonomyDefinitionLocales);
2515
+ } catch (error) {
2516
+ console.warn("[i18n] taxonomy locale diagnostic failed:", error);
2517
+ }
2518
+ const manifestHash = await hashString(JSON.stringify(manifestCollections) + JSON.stringify(manifestPlugins) + JSON.stringify(manifestTaxonomies));
2519
+ const authMode = getAuthMode(this.config);
2520
+ const authModeValue = authMode.type === "external" ? authMode.providerType : "passkey";
2521
+ const i18nConfig = virtualConfig?.i18n ?? getI18nConfig();
2522
+ const i18n = i18nConfig && i18nConfig.locales && i18nConfig.locales.length > 1 ? {
2523
+ defaultLocale: i18nConfig.defaultLocale,
2524
+ locales: i18nConfig.locales,
2525
+ prefixDefaultLocale: i18nConfig.prefixDefaultLocale
2526
+ } : void 0;
2527
+ const { registry, error: registryConfigurationError } = resolveManifestRegistryConfig(getRegistryConfigInput(this.config.registry));
2528
+ if (registryConfigurationError) console.error(`EmDash registry configuration error in ${registryConfigurationError.field} (${registryConfigurationError.code})`);
2529
+ return {
2530
+ version: VERSION,
2531
+ commit: COMMIT,
2532
+ astroVersion: this.config.astroVersion,
2533
+ hash: manifestHash,
2534
+ collections: manifestCollections,
2535
+ plugins: manifestPlugins,
2536
+ taxonomies: manifestTaxonomies,
2537
+ authMode: authModeValue,
2538
+ i18n,
2539
+ contentLocale: {
2540
+ defaultLocale: i18nConfig?.defaultLocale ?? "en",
2541
+ implicit: i18nConfig === null
2542
+ },
2543
+ marketplace: !!this.config.marketplace,
2544
+ sandboxEnabled: this.runtimeDeps.sandboxEnabled && this.runtimeDeps.sandboxBypassed !== true,
2545
+ registry,
2546
+ registryConfigurationError
2547
+ };
2548
+ }
2549
+ /**
2550
+ * Verify and repair FTS indexes on demand. Runs at most once per worker
2551
+ * lifetime.
2552
+ *
2553
+ * Originally called from `EmDashRuntime.create()`, but on a busy D1 link
2554
+ * (e.g. SIN replica ~80-150ms per query) it added ~1.5s to every cold
2555
+ * start for a modest-sized site — more than every other init phase
2556
+ * combined. Anonymous public reads never touch the search write path,
2557
+ * so the cost isn't paid back for the vast majority of requests.
2558
+ *
2559
+ * Instead, search endpoints call this lazily: the first request that
2560
+ * actually needs the index pays the verify cost (usually fast — no
2561
+ * rebuild needed), everyone else runs cold-free.
2562
+ *
2563
+ * Uses the runtime's singleton database (`this._db`) rather than the
2564
+ * request-scoped DB. Verify reads only, but `rebuildIndex` writes, and
2565
+ * a GET search request on D1 carries a `first-unconstrained` session
2566
+ * that's free to route at a read replica — unsafe for writes. The
2567
+ * singleton always goes through the default binding, which the D1
2568
+ * adapter will promote to `first-primary` for write statements.
2569
+ *
2570
+ * Safe to call concurrently: repeated callers share the same in-flight
2571
+ * promise. Errors are swallowed internally so callers don't need to
2572
+ * defend against FTS not existing yet (pre-setup).
2573
+ */
2574
+ async ensureSearchHealthy() {
2575
+ if (!isSqlite(this._db)) return;
2576
+ try {
2577
+ await singleFlightCached(this._searchHealthCache, async () => {
2578
+ try {
2579
+ const repaired = await new FTSManager(this._db).verifyAndRepairAll();
2580
+ if (repaired > 0) console.log(`Repaired ${repaired} corrupted FTS index(es)`);
2581
+ } catch {}
2582
+ }, {
2583
+ anchor: (promise) => after(() => promise),
2584
+ ownerTimeoutMs: 3e4
2585
+ });
2586
+ } catch {}
2587
+ }
2588
+ async handleContentList(collection, params) {
2589
+ return handleContentList(this.db, collection, params);
2590
+ }
2591
+ async handleContentAuthors(collection) {
2592
+ return handleContentAuthors(this.db, collection);
2593
+ }
2594
+ async handleContentGet(collection, id, locale, referenceOptions) {
2595
+ const result = await handleContentGet(this.db, collection, id, locale, referenceOptions);
2596
+ return this.hydrateDraftData(result);
2597
+ }
2598
+ async handleContentGetIncludingTrashed(collection, id, locale) {
2599
+ const result = await handleContentGetIncludingTrashed(this.db, collection, id, locale);
2600
+ return this.hydrateDraftData(result);
2601
+ }
2602
+ /**
2603
+ * If the response item has a `draftRevisionId`, replace `item.data` with
2604
+ * the draft revision's data and expose the original published values as
2605
+ * `liveData`. This makes the content_get / content_update round-trip
2606
+ * intuitive — read returns the latest content the caller has saved
2607
+ * (their pending draft), with the previously-published values still
2608
+ * accessible for compare-style flows.
2609
+ *
2610
+ * No-op when no draft exists or the response is an error.
2611
+ */
2612
+ async hydrateDraftData(result, options = {}) {
2613
+ if (!result || typeof result !== "object") return result;
2614
+ const r = result;
2615
+ if (!r.success || !r.data?.item) return result;
2616
+ const item = r.data.item;
2617
+ const draftRevisionId = typeof item.draftRevisionId === "string" ? item.draftRevisionId : null;
2618
+ if (!draftRevisionId) return result;
2619
+ try {
2620
+ const revision = await new RevisionRepository(this.db).findById(draftRevisionId);
2621
+ if (!revision) {
2622
+ if (options.strict) throw new Error(`Draft revision not found: ${draftRevisionId}`);
2623
+ return result;
2624
+ }
2625
+ const liveData = item.data && typeof item.data === "object" ? item.data : {};
2626
+ const revisionData = {};
2627
+ for (const [key, value] of Object.entries(revision.data)) if (!key.startsWith("_")) revisionData[key] = value;
2628
+ const mergedData = {
2629
+ ...liveData,
2630
+ ...revisionData
2631
+ };
2632
+ return {
2633
+ ...result,
2634
+ data: {
2635
+ ...r.data,
2636
+ item: {
2637
+ ...item,
2638
+ ...options.includeStagedSlug && typeof revision.data._slug === "string" ? { slug: revision.data._slug } : {},
2639
+ data: mergedData,
2640
+ liveData
2641
+ }
2642
+ }
2643
+ };
2644
+ } catch (error) {
2645
+ if (options.strict) throw error;
2646
+ console.error("[emdash] draft hydration failed:", error);
2647
+ return result;
2648
+ }
2649
+ }
2650
+ async handleContentCreate(collection, body, options = {}) {
2651
+ const actor = body.actor ? { ...body.actor } : void 0;
2652
+ let locale;
2653
+ try {
2654
+ locale = resolveContentCreateLocale(body.locale);
2655
+ } catch (error) {
2656
+ return {
2657
+ success: false,
2658
+ error: {
2659
+ code: "VALIDATION_ERROR",
2660
+ message: error instanceof Error ? error.message : "Invalid locale"
2661
+ }
2662
+ };
2663
+ }
2664
+ let translationSource = null;
2665
+ let sharedFieldSlugs = [];
2666
+ if (body.translationOf) {
2667
+ const repo = new ContentRepository(this.db);
2668
+ const source = await repo.findById(collection, body.translationOf);
2669
+ if (!source) return {
2670
+ success: false,
2671
+ error: {
2672
+ code: "NOT_FOUND",
2673
+ message: "Translation source content not found"
2674
+ }
2675
+ };
2676
+ translationSource = source;
2677
+ sharedFieldSlugs = (await this.db.selectFrom("_emdash_fields as field").innerJoin("_emdash_collections as collection", "collection.id", "field.collection_id").select([
2678
+ "field.slug",
2679
+ "field.type",
2680
+ "field.validation"
2681
+ ]).where("collection.slug", "=", collection).where("field.translatable", "=", 0).execute()).filter((field) => !isStoragelessFieldRow(field)).map((field) => field.slug);
2682
+ if ((await repo.findTranslations(collection, source.translationGroup ?? source.id)).some((sibling) => sibling.locale?.toLowerCase() === locale.toLowerCase())) return {
2683
+ success: false,
2684
+ error: {
2685
+ code: "CONFLICT",
2686
+ message: `Translation already exists in locale "${locale}" for this content item`
2687
+ }
2688
+ };
2689
+ }
2690
+ const saveHookDetails = {
2691
+ locale,
2692
+ ...body.translationOf ? { translationOf: body.translationOf } : {}
2693
+ };
2694
+ const collectionInfo = await this.schemaRegistry.getCollectionWithFields(collection).catch(() => null);
2695
+ let processedData = body.data;
2696
+ if (!options.skipSaveHooks && this.hooks.hasHooks("content:beforeSave")) {
2697
+ const hookData = collectionInfo ? await inferBlockTypes(collectionInfo, body.data, () => Promise.resolve(translationSource?.data)) : body.data;
2698
+ try {
2699
+ processedData = (await this.hooks.runContentBeforeSave(hookData, collection, true, void 0, actor, saveHookDetails)).content;
2700
+ } catch (error) {
2701
+ return beforeSaveFailure(error);
2702
+ }
2703
+ }
2704
+ if (translationSource) {
2705
+ processedData = { ...processedData };
2706
+ for (const slug of sharedFieldSlugs) if (Object.hasOwn(translationSource.data, slug)) processedData[slug] = translationSource.data[slug];
2707
+ else delete processedData[slug];
2708
+ }
2709
+ const resolvedBlockTypes = collectionInfo?.fields.some((field) => field.type === "blocks") ? await resolveBlockTypes(this.db) : void 0;
2710
+ if (collectionInfo) try {
2711
+ processedData = await normalizeBlocksData(this.db, collectionInfo, processedData, translationSource?.data, {
2712
+ migrateBlocks: body.migrateBlocks,
2713
+ replaceBlocks: body.replaceBlocks
2714
+ }, false, resolvedBlockTypes);
2715
+ } catch (error) {
2716
+ if (error instanceof SchemaError) return {
2717
+ success: false,
2718
+ error: {
2719
+ code: error.code,
2720
+ message: error.message,
2721
+ details: error.details
2722
+ }
2723
+ };
2724
+ throw error;
2725
+ }
2726
+ processedData = await this.normalizeFieldValues(collection, processedData, collectionInfo, resolvedBlockTypes);
2727
+ const { validateContentData } = await import("./validation-Ckq4vsVg.mjs");
2728
+ const validation = await validateContentData(this.db, collection, processedData, { partial: false });
2729
+ if (!validation.ok) return {
2730
+ success: false,
2731
+ error: validation.error
2732
+ };
2733
+ const { actor: _discardedActor, migrateBlocks: _discardedMigrateBlocks, replaceBlocks: _discardedReplaceBlocks, ...contentBody } = body;
2734
+ const result = await handleContentCreate(this.db, collection, {
2735
+ ...contentBody,
2736
+ data: processedData,
2737
+ locale,
2738
+ authorId: body.authorId,
2739
+ bylines: body.bylines
2740
+ });
2741
+ if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
2742
+ if (!options.skipSaveHooks && result.success && result.data) this.runAfterSaveHooks(contentItemToRecord(result.data.item), collection, true, actor, options.excludeAfterSavePluginId, {
2743
+ ...saveHookDetails,
2744
+ locale: result.data.item.locale ?? locale
2745
+ });
2746
+ return result;
2747
+ }
2748
+ async handleContentUpdate(collection, id, body) {
2749
+ const actor = body.actor ? { ...body.actor } : void 0;
2750
+ const repo = new ContentRepository(this.db);
2751
+ const resolvedItem = await repo.findByIdOrSlug(collection, id, body.locale);
2752
+ const resolvedId = resolvedItem?.id ?? id;
2753
+ if (body._rev) {
2754
+ if (!resolvedItem) return {
2755
+ success: false,
2756
+ error: {
2757
+ code: "NOT_FOUND",
2758
+ message: `Content item not found: ${id}`
2759
+ }
2760
+ };
2761
+ const revCheck = validateRev(body._rev, resolvedItem);
2762
+ if (!revCheck.valid) return {
2763
+ success: false,
2764
+ error: {
2765
+ code: "CONFLICT",
2766
+ message: revCheck.message
2767
+ }
2768
+ };
2769
+ }
2770
+ const { _rev: _discardedRev, actor: _discardedActor, migrateBlocks, replaceBlocks, ...bodyWithoutRev } = body;
2771
+ const blockWriteOptions = {
2772
+ migrateBlocks,
2773
+ replaceBlocks
2774
+ };
2775
+ const collectionInfo = bodyWithoutRev.data || bodyWithoutRev.references ? await this.schemaRegistry.getCollectionWithFields(collection).catch(() => null) : null;
2776
+ const knownFieldSlugs = new Set((collectionInfo?.fields ?? []).map((f) => f.slug));
2777
+ if (bodyWithoutRev.data && collectionInfo) {
2778
+ const changed = changedStoragelessDataKeys(new Set(collectionInfo.fields.filter(isStoragelessField).map((field) => field.slug)), bodyWithoutRev.data, resolvedItem?.data ?? {});
2779
+ if (changed.length > 0) return storagelessDataKeyError(changed);
2780
+ }
2781
+ let processedData = bodyWithoutRev.data;
2782
+ let resolvedBlockTypes;
2783
+ if (bodyWithoutRev.data) {
2784
+ if (this.hooks.hasHooks("content:beforeSave")) {
2785
+ const hookData = collectionInfo ? await inferBlockTypes(collectionInfo, bodyWithoutRev.data, async () => {
2786
+ if (resolvedItem?.draftRevisionId && collectionInfo.supports?.includes("revisions")) return (await new RevisionRepository(this.db).findById(resolvedItem.draftRevisionId))?.data ?? resolvedItem.data;
2787
+ return resolvedItem?.data;
2788
+ }) : bodyWithoutRev.data;
2789
+ try {
2790
+ processedData = (await this.hooks.runContentBeforeSave(hookData, collection, false, resolvedItem?.id, actor, resolvedItem?.locale ? { locale: resolvedItem.locale } : {})).content;
2791
+ } catch (error) {
2792
+ return beforeSaveFailure(error);
2793
+ }
2794
+ }
2795
+ processedData = await this.normalizeFieldValues(collection, processedData, collectionInfo, void 0, false);
2796
+ if (collectionInfo?.fields) processedData = await this.dropUnknownKeysAlreadyStored(processedData, resolvedItem, knownFieldSlugs);
2797
+ if (collectionInfo?.fields.some((field) => field.type === "blocks" && Object.hasOwn(processedData, field.slug))) resolvedBlockTypes = await resolveBlockTypes(this.db);
2798
+ if (!collectionInfo?.supports?.includes("revisions")) {
2799
+ if (collectionInfo) try {
2800
+ processedData = await normalizeBlocksData(this.db, collectionInfo, processedData, resolvedItem?.data, blockWriteOptions, true, resolvedBlockTypes);
2801
+ processedData = await this.normalizeFieldValues(collection, processedData, collectionInfo, resolvedBlockTypes);
2802
+ } catch (error) {
2803
+ if (error instanceof SchemaError) return {
2804
+ success: false,
2805
+ error: {
2806
+ code: error.code,
2807
+ message: error.message,
2808
+ details: error.details
2809
+ }
2810
+ };
2811
+ throw error;
2812
+ }
2813
+ const { validateContentData } = await import("./validation-Ckq4vsVg.mjs");
2814
+ const validation = await validateContentData(this.db, collection, processedData, { partial: true });
2815
+ if (!validation.ok) return {
2816
+ success: false,
2817
+ error: validation.error
2818
+ };
2819
+ }
2820
+ }
2821
+ let usesDraftRevisions = false;
2822
+ let draftStorageChanged = false;
2823
+ if (processedData || bodyWithoutRev.references) {
2824
+ if (collectionInfo?.supports?.includes("revisions")) {
2825
+ usesDraftRevisions = true;
2826
+ let stagedReferences;
2827
+ let stagedReferenceBaselines;
2828
+ if (bodyWithoutRev.references) {
2829
+ stagedReferences = {};
2830
+ let entryGroup;
2831
+ for (const [fieldSlug, selectedIds] of Object.entries(bodyWithoutRev.references)) {
2832
+ const resolved = await resolveReferenceSelection(this.db, collection, resolvedId, fieldSlug, selectedIds);
2833
+ if (!resolved.success) return {
2834
+ success: false,
2835
+ error: resolved.error
2836
+ };
2837
+ stagedReferences[fieldSlug] = resolved.data.groups;
2838
+ entryGroup = resolved.data.entryGroup;
2839
+ }
2840
+ if (entryGroup) {
2841
+ const liveSelection = await liveReferenceSelection(this.db, collection, entryGroup);
2842
+ stagedReferenceBaselines = {};
2843
+ for (const fieldSlug of Object.keys(stagedReferences)) stagedReferenceBaselines[fieldSlug] = liveSelection[fieldSlug] ?? [];
2844
+ }
2845
+ }
2846
+ const revisionRepo = new RevisionRepository(this.db);
2847
+ let existing = await repo.findById(collection, resolvedId);
2848
+ if (body._rev && existing) {
2849
+ const revCheck = validateRev(body._rev, existing);
2850
+ if (!revCheck.valid) return {
2851
+ success: false,
2852
+ error: {
2853
+ code: "CONFLICT",
2854
+ message: revCheck.message
2855
+ }
2856
+ };
2857
+ }
2858
+ for (let attempt = 0; existing && attempt < MAX_DRAFT_STAGE_ATTEMPTS; attempt++) {
2859
+ let baseData;
2860
+ if (existing.draftRevisionId) baseData = (await revisionRepo.findById(existing.draftRevisionId))?.data ?? existing.data;
2861
+ else baseData = existing.data;
2862
+ let attemptData = processedData ?? {};
2863
+ try {
2864
+ attemptData = await normalizeBlocksData(this.db, collectionInfo, attemptData, baseData, blockWriteOptions, true, resolvedBlockTypes);
2865
+ attemptData = await this.normalizeFieldValues(collection, attemptData, collectionInfo, resolvedBlockTypes);
2866
+ } catch (error) {
2867
+ if (error instanceof SchemaError) return {
2868
+ success: false,
2869
+ error: {
2870
+ code: error.code,
2871
+ message: error.message,
2872
+ details: error.details
2873
+ }
2874
+ };
2875
+ throw error;
2876
+ }
2877
+ const { validateContentData } = await import("./validation-Ckq4vsVg.mjs");
2878
+ const validation = await validateContentData(this.db, collection, attemptData, { partial: true });
2879
+ if (!validation.ok) return {
2880
+ success: false,
2881
+ error: validation.error
2882
+ };
2883
+ const mergedData = collectionInfo?.fields ? keepKnownFields({
2884
+ ...baseData,
2885
+ ...attemptData
2886
+ }, knownFieldSlugs) : {
2887
+ ...baseData,
2888
+ ...attemptData
2889
+ };
2890
+ if (bodyWithoutRev.slug !== void 0) mergedData._slug = bodyWithoutRev.slug;
2891
+ if (stagedReferences) mergedData[STAGED_REFERENCES_KEY] = mergeStagedReferences(baseData, stagedReferences);
2892
+ if (stagedReferenceBaselines) mergedData[STAGED_REFERENCES_BASELINE_KEY] = mergeStagedReferenceBaselines(baseData, stagedReferenceBaselines);
2893
+ const revision = await revisionRepo.create({
2894
+ collection,
2895
+ entryId: resolvedId,
2896
+ data: mergedData,
2897
+ authorId: actor?.id
2898
+ });
2899
+ let staged;
2900
+ try {
2901
+ staged = await repo.replaceDraftRevision(collection, resolvedId, revision.id, existing);
2902
+ } catch (error) {
2903
+ try {
2904
+ await revisionRepo.deleteIfUnreferenced(collection, resolvedId, revision.id);
2905
+ } catch (cleanupError) {
2906
+ console.error(`[emdash] Failed to clean up unstaged revision ${revision.id}:`, cleanupError);
2907
+ }
2908
+ throw error;
2909
+ }
2910
+ if (!staged) {
2911
+ try {
2912
+ await revisionRepo.deleteIfUnreferenced(collection, resolvedId, revision.id);
2913
+ } catch (cleanupError) {
2914
+ console.error(`[emdash] Failed to clean up unstaged revision ${revision.id}:`, cleanupError);
2915
+ }
2916
+ if (body._rev || attempt === MAX_DRAFT_STAGE_ATTEMPTS - 1) return {
2917
+ success: false,
2918
+ error: {
2919
+ code: "CONFLICT",
2920
+ message: new ContentMutationConflictError().message
2921
+ }
2922
+ };
2923
+ existing = await repo.findById(collection, resolvedId);
2924
+ continue;
2925
+ }
2926
+ draftStorageChanged = true;
2927
+ processedData = attemptData;
2928
+ if (bodyWithoutRev.skipRevision && existing.draftRevisionId) try {
2929
+ await revisionRepo.deleteIfUnreferenced(collection, resolvedId, existing.draftRevisionId);
2930
+ } catch (error) {
2931
+ console.error(`[emdash] Failed to clean up superseded revision ${existing.draftRevisionId}:`, error);
2932
+ }
2933
+ else after(async () => {
2934
+ try {
2935
+ await revisionRepo.pruneQueuedEntry(collection, resolvedId, revision.id, 50);
2936
+ } catch (error) {
2937
+ console.error(`[revisions] Failed to prune revisions for ${collection}/${resolvedId}:`, error);
2938
+ }
2939
+ });
2940
+ break;
2941
+ }
2942
+ }
2943
+ }
2944
+ const liveMetaTouched = Object.entries(bodyWithoutRev).some(([key, value]) => value !== void 0 && !DRAFT_ONLY_UPDATE_KEYS.has(key));
2945
+ const result = usesDraftRevisions && !liveMetaTouched ? await handleContentGet(this.db, collection, resolvedId) : await handleContentUpdate(this.db, collection, resolvedId, {
2946
+ ...bodyWithoutRev,
2947
+ _rev: draftStorageChanged ? void 0 : body._rev,
2948
+ data: usesDraftRevisions ? void 0 : processedData,
2949
+ slug: usesDraftRevisions ? void 0 : bodyWithoutRev.slug,
2950
+ references: usesDraftRevisions ? void 0 : bodyWithoutRev.references,
2951
+ authorId: bodyWithoutRev.authorId,
2952
+ bylines: bodyWithoutRev.bylines
2953
+ });
2954
+ const liveContentChanged = usesDraftRevisions ? liveMetaTouched : Boolean(processedData || bodyWithoutRev.slug !== void 0 || bodyWithoutRev.references || liveMetaTouched);
2955
+ const hydrated = await this.hydrateDraftData(result);
2956
+ if (hydrated.success && hydrated.data) {
2957
+ const contentIdsToRefresh = [resolvedId];
2958
+ if (!usesDraftRevisions && processedData) contentIdsToRefresh.push(...await this.findSyncedSiblingsForUsageRefresh(collection, resolvedId, hydrated.data.item.translationGroup, processedData));
2959
+ await this.refreshContentUsageAfterSuccessfulWrite(collection, contentIdsToRefresh);
2960
+ } else if (draftStorageChanged) try {
2961
+ await markContentMediaUsageCollectionStale(this.db, collection, "CONTENT_USAGE_STALE");
2962
+ } catch (error) {
2963
+ console.error(`[media-usage] Failed to mark ${collection} stale:`, error);
2964
+ }
2965
+ if (hydrated.success && hydrated.data) {
2966
+ const savedLocale = hydrated.data.item.locale;
2967
+ this.runAfterSaveHooks(contentItemToRecord(hydrated.data.item), collection, false, actor, void 0, savedLocale ? { locale: savedLocale } : {});
2968
+ }
2969
+ if (hydrated.success) return {
2970
+ ...hydrated,
2971
+ liveContentChanged
2972
+ };
2973
+ return hydrated;
2974
+ }
2975
+ async handleContentDelete(collection, id) {
2976
+ if (this.hooks.hasHooks("content:beforeDelete")) {
2977
+ const { allowed } = await this.hooks.runContentBeforeDelete(id, collection);
2978
+ if (!allowed) return {
2979
+ success: false,
2980
+ error: {
2981
+ code: "DELETE_BLOCKED",
2982
+ message: "Delete blocked by plugin hook"
2983
+ }
2984
+ };
2985
+ }
2986
+ const policyRejectionRevision = await this.getScheduledPolicyRejectionRevision(collection, id);
2987
+ const result = await handleContentDelete(this.db, collection, id);
2988
+ if (result.success) {
2989
+ await this.clearScheduledPolicyRejection(collection, result.data.id, policyRejectionRevision);
2990
+ await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.id]);
2991
+ }
2992
+ if (result.success) this.runAfterDeleteHooks(id, collection, false);
2993
+ return result;
2994
+ }
2995
+ async handleContentListTrashed(collection, params = {}) {
2996
+ return handleContentListTrashed(this.db, collection, params);
2997
+ }
2998
+ async handleContentRestore(collection, id, options = {}) {
2999
+ const result = await handleContentRestore(this.db, collection, id, { _rev: options._rev });
3000
+ if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
3001
+ if (result.success) this.runAfterRestoreHooks(contentItemToRecord(result.data.item), collection, options.origin?.source === "plugin" ? options.origin.pluginId : void 0, options.pluginInvocationId);
3002
+ return result;
3003
+ }
3004
+ async handleContentPermanentDelete(collection, id) {
3005
+ const policyRejectionRevision = await this.getScheduledPolicyRejectionRevision(collection, id);
3006
+ const result = await handleContentPermanentDelete(this.db, collection, id);
3007
+ if (result.success) {
3008
+ await this.clearScheduledPolicyRejection(collection, result.data.id, policyRejectionRevision);
3009
+ await this.deleteContentUsageAfterSuccessfulPermanentDelete(collection, result.data.id);
3010
+ }
3011
+ if (result.success) this.runAfterDeleteHooks(id, collection, true);
3012
+ return result;
3013
+ }
3014
+ async handleContentCountTrashed(collection, params = {}) {
3015
+ return handleContentCountTrashed(this.db, collection, params);
3016
+ }
3017
+ async handleContentDuplicate(collection, id, authorId) {
3018
+ const result = await handleContentDuplicate(this.db, collection, id, authorId);
3019
+ if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
3020
+ return result;
3021
+ }
3022
+ pluginVersionedResult(result) {
3023
+ if (!result.success) throw Object.assign(new Error(result.error.message), { code: result.error.code });
3024
+ if (!result.data._rev) throw new Error("Content action returned no revision");
3025
+ return {
3026
+ item: toPluginContentItem(result.data.item),
3027
+ _rev: result.data._rev
3028
+ };
3029
+ }
3030
+ async runPluginContentAction(pluginId, action, collection, id, fn, invocationId, invalidateContentCache) {
3031
+ let invocationInvalidator;
3032
+ if (invocationId) {
3033
+ const invocationKey = this.pluginInvocationKey(pluginId, invocationId);
3034
+ if (!this.activePluginInvocations.has(invocationKey) && !this.releasedPluginInvocations.has(invocationKey)) throw Object.assign(/* @__PURE__ */ new Error("Plugin content action invocation has expired"), { code: "CONTENT_ACTION_INVOCATION_EXPIRED" });
3035
+ invocationInvalidator = this.pluginInvocationCacheInvalidators.get(invocationKey);
3036
+ }
3037
+ const recordWrite = await assertSiteWriteAllowed(this.db);
3038
+ const repo = new ContentRepository(this.db);
3039
+ const resolvedId = (action === "restore" ? await repo.findByIdOrSlugIncludingTrashed(collection, id) : await repo.findByIdOrSlug(collection, id))?.id ?? id;
3040
+ const key = this.pluginContentActionKey(collection, resolvedId);
3041
+ if ((this.activePluginContentActions.get(key) ?? 0) > 0) throw Object.assign(/* @__PURE__ */ new Error("Plugin content action re-entered itself"), { code: "CONTENT_ACTION_REENTRANT" });
3042
+ this.retainPluginContentAction(key);
3043
+ try {
3044
+ const result = await fn(resolvedId);
3045
+ await recordWrite();
3046
+ const invalidator = invalidateContentCache ?? invocationInvalidator ?? this.pluginContentCacheInvalidator;
3047
+ if (invalidator) try {
3048
+ await invalidator([collection, resolvedId]);
3049
+ } catch (error) {
3050
+ console.error(`[plugin-content-action] Cache invalidation failed for ${collection}/${resolvedId}:`, error);
3051
+ }
3052
+ return result;
3053
+ } finally {
3054
+ this.releasePluginContentAction(key);
3055
+ }
3056
+ }
3057
+ pluginContentActionKey(collection, id) {
3058
+ return JSON.stringify([collection, id]);
3059
+ }
3060
+ retainPluginContentAction(key) {
3061
+ this.activePluginContentActions.set(key, (this.activePluginContentActions.get(key) ?? 0) + 1);
3062
+ }
3063
+ releasePluginContentAction(key) {
3064
+ const remaining = (this.activePluginContentActions.get(key) ?? 1) - 1;
3065
+ if (remaining > 0) this.activePluginContentActions.set(key, remaining);
3066
+ else this.activePluginContentActions.delete(key);
3067
+ }
3068
+ async pluginGetVersioned(_pluginId, collection, id) {
3069
+ const result = await this.handleContentGet(collection, id);
3070
+ if (!result.success) {
3071
+ if (result.error.code === "NOT_FOUND") return null;
3072
+ throw Object.assign(new Error(result.error.message), { code: result.error.code });
3073
+ }
3074
+ if (!result.data._rev) throw new Error("Content read returned no revision");
3075
+ return {
3076
+ item: toPluginContentItem(result.data.item),
3077
+ _rev: result.data._rev
3078
+ };
3079
+ }
3080
+ async pluginGetTrashedVersioned(_pluginId, collection, id) {
3081
+ const result = await this.handleContentGetIncludingTrashed(collection, id);
3082
+ if (!result.success) {
3083
+ if (result.error.code === "NOT_FOUND") return null;
3084
+ throw Object.assign(new Error(result.error.message), { code: result.error.code });
3085
+ }
3086
+ if (!await new ContentRepository(this.db).isTrashed(collection, result.data.item.id)) return null;
3087
+ if (!result.data._rev) throw new Error("Trashed content read returned no revision");
3088
+ return {
3089
+ item: toPluginContentItem(result.data.item),
3090
+ _rev: result.data._rev
3091
+ };
3092
+ }
3093
+ pluginPublish(pluginId, collection, id, options, invocationId, invalidateContentCache) {
3094
+ return this.runPluginContentAction(pluginId, "publish", collection, id, async (resolvedId) => this.pluginVersionedResult(await this.handleContentPublish(collection, resolvedId, {
3095
+ _rev: options._rev,
3096
+ origin: {
3097
+ source: "plugin",
3098
+ pluginId
3099
+ },
3100
+ pluginInvocationId: invocationId
3101
+ })), invocationId, invalidateContentCache);
3102
+ }
3103
+ pluginUnpublish(pluginId, collection, id, options, invocationId, invalidateContentCache) {
3104
+ return this.runPluginContentAction(pluginId, "unpublish", collection, id, async (resolvedId) => this.pluginVersionedResult(await this.handleContentUnpublish(collection, resolvedId, {
3105
+ _rev: options._rev,
3106
+ origin: {
3107
+ source: "plugin",
3108
+ pluginId
3109
+ },
3110
+ pluginInvocationId: invocationId
3111
+ })), invocationId, invalidateContentCache);
3112
+ }
3113
+ pluginSchedule(pluginId, collection, id, options, invocationId, invalidateContentCache) {
3114
+ return this.runPluginContentAction(pluginId, "schedule", collection, id, async (resolvedId) => this.pluginVersionedResult(await this.handleContentSchedule(collection, resolvedId, options.scheduledAt, {
3115
+ _rev: options._rev,
3116
+ origin: {
3117
+ source: "plugin",
3118
+ pluginId
3119
+ },
3120
+ pluginInvocationId: invocationId
3121
+ })), invocationId, invalidateContentCache);
3122
+ }
3123
+ pluginUnschedule(pluginId, collection, id, options, invocationId, invalidateContentCache) {
3124
+ return this.runPluginContentAction(pluginId, "unschedule", collection, id, async (resolvedId) => this.pluginVersionedResult(await this.handleContentUnschedule(collection, resolvedId, {
3125
+ ...options,
3126
+ origin: {
3127
+ source: "plugin",
3128
+ pluginId
3129
+ },
3130
+ pluginInvocationId: invocationId
3131
+ })), invocationId, invalidateContentCache);
3132
+ }
3133
+ pluginRestore(pluginId, collection, id, options, invocationId, invalidateContentCache) {
3134
+ return this.runPluginContentAction(pluginId, "restore", collection, id, async (resolvedId) => this.pluginVersionedResult(await this.handleContentRestore(collection, resolvedId, {
3135
+ ...options,
3136
+ origin: {
3137
+ source: "plugin",
3138
+ pluginId
3139
+ },
3140
+ pluginInvocationId: invocationId
3141
+ })), invocationId, invalidateContentCache);
3142
+ }
3143
+ async checkContentPolicy(name, collection, id, options, rejectionCode, failureCode, scheduledAt) {
3144
+ let current = await handleContentGet(this.db, collection, id);
3145
+ if (!current.success) return {
3146
+ allowed: false,
3147
+ revision: options._rev ?? "",
3148
+ error: current.error
3149
+ };
3150
+ const revision = current.data._rev;
3151
+ if (!revision) return {
3152
+ allowed: false,
3153
+ revision: options._rev ?? "",
3154
+ error: {
3155
+ code: failureCode,
3156
+ message: "Failed to read content revision"
3157
+ }
3158
+ };
3159
+ if (options._rev !== void 0 && options._rev !== revision) return {
3160
+ allowed: false,
3161
+ revision,
3162
+ error: {
3163
+ code: "CONFLICT",
3164
+ message: "Revision precondition did not match"
3165
+ }
3166
+ };
3167
+ if (!this.hooks.hasHooks(name)) return {
3168
+ allowed: true,
3169
+ revision
3170
+ };
3171
+ if (name !== "content:beforeUnpublish") try {
3172
+ current = await this.hydrateDraftData(current, {
3173
+ includeStagedSlug: true,
3174
+ strict: true
3175
+ });
3176
+ } catch (error) {
3177
+ console.error(`[content-policy] ${name} draft hydration failed:`, error);
3178
+ return {
3179
+ allowed: false,
3180
+ revision,
3181
+ error: {
3182
+ code: failureCode,
3183
+ message: `Failed to read content for ${name} policy`
3184
+ }
3185
+ };
3186
+ }
3187
+ const origin = options.origin ?? { source: "system" };
3188
+ const actor = options.actor && (origin.source === "api" || origin.source === "mcp" || origin.source === "visual-editor") ? {
3189
+ ...options.actor,
3190
+ source: origin.source
3191
+ } : void 0;
3192
+ try {
3193
+ const decision = await this.hooks.runContentPolicy(name, {
3194
+ content: contentItemToRecord(current.data.item),
3195
+ collection,
3196
+ origin,
3197
+ actor,
3198
+ ...scheduledAt === void 0 ? {} : { scheduledAt }
3199
+ });
3200
+ if (decision.cancellation) return {
3201
+ allowed: false,
3202
+ revision,
3203
+ error: {
3204
+ code: rejectionCode,
3205
+ message: decision.cancellation.reason
3206
+ },
3207
+ cancellation: decision.cancellation
3208
+ };
3209
+ return {
3210
+ allowed: true,
3211
+ revision
3212
+ };
3213
+ } catch (error) {
3214
+ console.error(`[content-policy] ${name} failed:`, error);
3215
+ return {
3216
+ allowed: false,
3217
+ revision,
3218
+ error: {
3219
+ code: failureCode,
3220
+ message: `Failed to run ${name} policy`
3221
+ }
3222
+ };
3223
+ }
3224
+ }
3225
+ async getScheduledPolicyRejectionRevision(collection, id) {
3226
+ try {
3227
+ return (await new OptionsRepository(this.db).getVersioned(scheduledPolicyRejectionKey(collection, id)))?.revision;
3228
+ } catch (error) {
3229
+ console.error(`[content-policy] Failed to read scheduled rejection for ${collection}/${id}:`, error);
3230
+ return;
3231
+ }
3232
+ }
3233
+ async clearScheduledPolicyRejection(collection, id, expectedRevision) {
3234
+ if (expectedRevision === void 0) return;
3235
+ try {
3236
+ await new OptionsRepository(this.db).compareAndDelete(scheduledPolicyRejectionKey(collection, id), expectedRevision);
3237
+ } catch (error) {
3238
+ console.error(`[content-policy] Failed to clear scheduled rejection for ${collection}/${id}:`, error);
3239
+ }
3240
+ }
3241
+ createScheduledPolicyRejection(collection, id, cancellation) {
3242
+ return {
3243
+ collection,
3244
+ id,
3245
+ pluginId: cancellation.pluginId,
3246
+ reason: cancellation.reason,
3247
+ rejectedAt: (this.runtimeDeps.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
3248
+ };
3249
+ }
3250
+ async handleContentPublish(collection, id, options = {}) {
3251
+ const policy = await this.checkContentPolicy("content:beforePublish", collection, id, options, "PUBLISH_REJECTED", "CONTENT_PUBLISH_ERROR");
3252
+ if (!policy.allowed) {
3253
+ if (policy.cancellation && options.origin?.source === "scheduler") {
3254
+ const unscheduled = await handleScheduledPolicyRejection(this.db, collection, id, {
3255
+ _rev: policy.revision,
3256
+ rejection: this.createScheduledPolicyRejection(collection, id, policy.cancellation)
3257
+ });
3258
+ if (!unscheduled.success) return unscheduled;
3259
+ await this.refreshContentUsageAfterSuccessfulWrite(collection, [unscheduled.data.item.id]);
3260
+ this.runAfterUnscheduleHooks(contentItemToRecord(unscheduled.data.item), collection);
3261
+ }
3262
+ return {
3263
+ success: false,
3264
+ error: policy.error
3265
+ };
3266
+ }
3267
+ const policyRejectionRevision = await this.getScheduledPolicyRejectionRevision(collection, id);
3268
+ const { actor: _actor, origin: _origin, pluginInvocationId: _pluginInvocationId, ...handlerOptions } = options;
3269
+ const result = await handleContentPublish(this.db, collection, id, {
3270
+ ...handlerOptions,
3271
+ _rev: policy.revision
3272
+ });
3273
+ if (result.success && result.data) {
3274
+ await this.clearScheduledPolicyRejection(collection, result.data.item.id, policyRejectionRevision);
3275
+ const { item } = result.data;
3276
+ await this.refreshContentUsageAfterSuccessfulWrite(collection, [item.id, ...await this.findSyncedSiblingsForUsageRefresh(collection, item.id, item.translationGroup, item.data, { absentAsCleared: true })]);
3277
+ }
3278
+ if (result.success && result.data) this.runAfterPublishHooks(contentItemToRecord(result.data.item), collection, options.origin?.source === "plugin" ? options.origin.pluginId : void 0, options.pluginInvocationId);
3279
+ return result;
3280
+ }
3281
+ async handleContentUnpublish(collection, id, options = {}) {
3282
+ const policy = await this.checkContentPolicy("content:beforeUnpublish", collection, id, options, "UNPUBLISH_REJECTED", "CONTENT_UNPUBLISH_ERROR");
3283
+ if (!policy.allowed) return {
3284
+ success: false,
3285
+ error: policy.error
3286
+ };
3287
+ const result = await handleContentUnpublish(this.db, collection, id, { _rev: policy.revision });
3288
+ if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
3289
+ if (result.success && result.data) this.runAfterUnpublishHooks(contentItemToRecord(result.data.item), collection, options.origin?.source === "plugin" ? options.origin.pluginId : void 0, options.pluginInvocationId);
3290
+ return result;
3291
+ }
3292
+ async handleContentSchedule(collection, id, scheduledAt, options = {}) {
3293
+ const policy = await this.checkContentPolicy("content:beforeSchedule", collection, id, options, "SCHEDULE_REJECTED", "CONTENT_SCHEDULE_ERROR", scheduledAt);
3294
+ if (!policy.allowed) return {
3295
+ success: false,
3296
+ error: policy.error
3297
+ };
3298
+ const policyRejectionRevision = await this.getScheduledPolicyRejectionRevision(collection, id);
3299
+ const result = await handleContentSchedule(this.db, collection, id, scheduledAt, this.runtimeDeps.now?.() ?? /* @__PURE__ */ new Date(), policy.revision);
3300
+ if (result.success && result.data) {
3301
+ await this.clearScheduledPolicyRejection(collection, result.data.item.id, policyRejectionRevision);
3302
+ await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
3303
+ }
3304
+ if (result.success && result.data) this.runAfterScheduleHooks(contentItemToRecord(result.data.item), collection, options.origin?.source === "plugin" ? options.origin.pluginId : void 0, options.pluginInvocationId);
3305
+ return result;
3306
+ }
3307
+ async handleContentUnschedule(collection, id, options = {}) {
3308
+ const result = await handleContentUnschedule(this.db, collection, id, { _rev: options._rev });
3309
+ if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
3310
+ if (result.success && result.data) this.runAfterUnscheduleHooks(contentItemToRecord(result.data.item), collection, options.origin?.source === "plugin" ? options.origin.pluginId : void 0, options.pluginInvocationId);
3311
+ return result;
3312
+ }
3313
+ async handleContentCountScheduled(collection) {
3314
+ return handleContentCountScheduled(this.db, collection);
3315
+ }
3316
+ async handleContentDiscardDraft(collection, id, options = {}) {
3317
+ const result = await handleContentDiscardDraft(this.db, collection, id, options);
3318
+ if (result.success && result.data) await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
3319
+ return result;
3320
+ }
3321
+ async handleContentCompare(collection, id) {
3322
+ return handleContentCompare(this.db, collection, id);
3323
+ }
3324
+ async handleContentTranslations(collection, id) {
3325
+ return handleContentTranslations(this.db, collection, id);
3326
+ }
3327
+ async handleMediaList(params) {
3328
+ return handleMediaList(this.db, params);
3329
+ }
3330
+ async handleMediaGet(id) {
3331
+ return handleMediaGet(this.db, id);
3332
+ }
3333
+ async handleMediaUpload(input) {
3334
+ if (!this.storage) return {
3335
+ success: false,
3336
+ error: {
3337
+ code: "NO_STORAGE",
3338
+ message: "Storage not configured"
3339
+ }
3340
+ };
3341
+ const result = await handleMediaUpload(this.db, this.storage, input, { beforeUpload: async (file) => {
3342
+ if (!this.hooks.hasHooks("media:beforeUpload")) return file;
3343
+ return (await this.hooks.runMediaBeforeUpload(file)).file;
3344
+ } });
3345
+ if (result.success && !result.data.deduplicated && this.hooks.hasHooks("media:afterUpload")) {
3346
+ const item = result.data.item;
3347
+ await this.hooks.runMediaAfterUpload({
3348
+ id: item.id,
3349
+ filename: item.filename,
3350
+ mimeType: item.mimeType,
3351
+ size: item.size,
3352
+ url: item.url,
3353
+ createdAt: item.createdAt
3354
+ });
3355
+ }
3356
+ return result;
3357
+ }
3358
+ async handleMediaRegisterUpload(input) {
3359
+ if (!this.storage) return {
3360
+ success: false,
3361
+ error: {
3362
+ code: "NO_STORAGE",
3363
+ message: "Storage not configured"
3364
+ }
3365
+ };
3366
+ const result = await handleMediaRegisterUpload(this.db, this.storage, input);
3367
+ if (result.success && this.hooks.hasHooks("media:afterUpload")) {
3368
+ const item = result.data.item;
3369
+ const mediaItem = {
3370
+ id: item.id,
3371
+ filename: item.filename,
3372
+ mimeType: item.mimeType,
3373
+ size: item.size,
3374
+ url: `/media/${item.id}/${item.filename}`,
3375
+ createdAt: item.createdAt
3376
+ };
3377
+ this.runMediaAfterUploadHook(mediaItem);
3378
+ }
3379
+ return result;
3380
+ }
3381
+ async handleMediaCreate(input) {
3382
+ let processedInput = input;
3383
+ if (this.hooks.hasHooks("media:beforeUpload")) {
3384
+ const hookResult = await this.hooks.runMediaBeforeUpload({
3385
+ name: input.filename,
3386
+ type: input.mimeType,
3387
+ size: input.size || 0
3388
+ });
3389
+ processedInput = {
3390
+ ...input,
3391
+ filename: hookResult.file.name,
3392
+ mimeType: hookResult.file.type,
3393
+ size: hookResult.file.size
3394
+ };
3395
+ }
3396
+ const result = await handleMediaCreate(this.db, processedInput);
3397
+ if (result.success && this.hooks.hasHooks("media:afterUpload")) {
3398
+ const item = result.data.item;
3399
+ const mediaItem = {
3400
+ id: item.id,
3401
+ filename: item.filename,
3402
+ mimeType: item.mimeType,
3403
+ size: item.size,
3404
+ url: `/media/${item.id}/${item.filename}`,
3405
+ createdAt: item.createdAt
3406
+ };
3407
+ this.runMediaAfterUploadHook(mediaItem);
3408
+ }
3409
+ return result;
3410
+ }
3411
+ async handleMediaUpdate(id, input) {
3412
+ const result = await handleMediaUpdate(this.db, id, input);
3413
+ if (result.success) invalidateSiteSettingsCache();
3414
+ return result;
3415
+ }
3416
+ async handlePluginMediaMetadataUpdate(id, patch) {
3417
+ return updatePluginMediaMetadata(this.db, id, patch);
3418
+ }
3419
+ async handleMediaReplaceMetadata(id, expectedStorageKey, input) {
3420
+ const result = await handleMediaReplaceMetadata(this.db, id, expectedStorageKey, input);
3421
+ if (result.success) invalidateSiteSettingsCache();
3422
+ return result;
3423
+ }
3424
+ async handleMediaDelete(id) {
3425
+ const result = await handleMediaDelete(this.db, id, this.storage);
3426
+ if (result.success) invalidateSiteSettingsCache();
3427
+ return result;
3428
+ }
3429
+ commentHooks() {
3430
+ return {
3431
+ runBeforeCreate: (event) => this.hooks.runCommentBeforeCreate(event),
3432
+ runModerate: async (event) => {
3433
+ const result = await this.hooks.invokeExclusiveHook("comment:moderate", event);
3434
+ if (!result) return {
3435
+ status: "pending",
3436
+ reason: "No moderator configured"
3437
+ };
3438
+ if (result.error) {
3439
+ console.error(`[comments] Moderation error (${result.pluginId}):`, result.error.message);
3440
+ return {
3441
+ status: "pending",
3442
+ reason: "Moderation error"
3443
+ };
3444
+ }
3445
+ if (!isModerationDecision(result.result)) return {
3446
+ status: "pending",
3447
+ reason: "Invalid moderation result"
3448
+ };
3449
+ return result.result;
3450
+ },
3451
+ fireAfterCreate: (event) => {
3452
+ after(() => this.hooks.runCommentAfterCreate(event).catch((error) => console.error("[comments] afterCreate error:", error instanceof Error ? error.message : error)));
3453
+ },
3454
+ fireAfterModerate: (event) => {
3455
+ return this.hooks.runCommentAfterModerate(event).catch((error) => console.error("[comments] afterModerate error:", error instanceof Error ? error.message : error));
3456
+ }
3457
+ };
3458
+ }
3459
+ async handleCommentCreate(input, settings, contentInfo) {
3460
+ return createComment(this.db, input, settings, this.commentHooks(), contentInfo);
3461
+ }
3462
+ async handlePublicCommentSubmission(collection, contentId, request, user) {
3463
+ return submitPublicComment(this, collection, contentId, request, user);
3464
+ }
3465
+ async sendCommentApproval(comment, request) {
3466
+ if (!comment || !this.email) return;
3467
+ try {
3468
+ const adminBaseUrl = await getSiteBaseUrl(this.db, request ?? new Request("https://plugin.invalid/"), this.config);
3469
+ const content = await lookupContentAuthor(this.db, comment.collection, comment.contentId);
3470
+ if (content?.author) await sendCommentNotification({
3471
+ email: this.email,
3472
+ comment,
3473
+ contentAuthor: content.author,
3474
+ adminBaseUrl
3475
+ });
3476
+ } catch (error) {
3477
+ console.error("[comments] notification error:", error instanceof Error ? error.message : error);
3478
+ }
3479
+ }
3480
+ async moderateCommentWithOrigin(id, status, expectedStatus, origin, request) {
3481
+ if (this.commentModerationInProgress.has(id)) {
3482
+ const current = await new CommentRepository(this.db).findById(id);
3483
+ if (current && current.status !== expectedStatus) throw new CommentStatusConflictError(current.status);
3484
+ throw Object.assign(/* @__PURE__ */ new Error("Comment moderation is already in progress"), { code: "COMMENT_MODERATION_IN_PROGRESS" });
3485
+ }
3486
+ this.commentModerationInProgress.add(id);
3487
+ try {
3488
+ return await moderateComment(this.db, id, status, expectedStatus, origin, this.commentHooks(), (comment) => this.sendCommentApproval(comment, request));
3489
+ } finally {
3490
+ this.commentModerationInProgress.delete(id);
3491
+ }
3492
+ }
3493
+ async handleCommentModerate(id, status, moderator, expectedStatus, request) {
3494
+ let expected = expectedStatus;
3495
+ if (!expected) {
3496
+ const current = await new CommentRepository(this.db).findById(id);
3497
+ if (!current || current.status === "trash") return null;
3498
+ expected = current.status;
3499
+ }
3500
+ return this.moderateCommentWithOrigin(id, status, expected, {
3501
+ source: "admin",
3502
+ userId: moderator.id,
3503
+ name: moderator.name
3504
+ }, request);
3505
+ }
3506
+ async handlePluginCommentModerate(pluginId, id, status, expectedStatus) {
3507
+ assertPluginCommentStatus(status, "status");
3508
+ assertPluginCommentStatus(expectedStatus, "expectedStatus");
3509
+ if (!await this.moderateCommentWithOrigin(id, status, expectedStatus, {
3510
+ source: "plugin",
3511
+ pluginId
3512
+ })) throw new Error(`Comment not found: ${id}`);
3513
+ const result = await createCommentAccess(this.db).get(id);
3514
+ if (!result) throw new Error(`Comment not found: ${id}`);
3515
+ return result;
3516
+ }
3517
+ async handleRevisionList(collection, entryId, params = {}) {
3518
+ return handleRevisionList(this.db, collection, entryId, params);
3519
+ }
3520
+ async handleRevisionGet(revisionId) {
3521
+ return handleRevisionGet(this.db, revisionId);
3522
+ }
3523
+ async handleRevisionRestore(revisionId, callerUserId) {
3524
+ const revisionRepo = new RevisionRepository(this.db);
3525
+ const revision = await revisionRepo.findById(revisionId);
3526
+ if (!revision) return {
3527
+ success: false,
3528
+ error: {
3529
+ code: "NOT_FOUND",
3530
+ message: `Revision not found: ${revisionId}`
3531
+ }
3532
+ };
3533
+ if (!((await this.schemaRegistry.getCollectionWithFields(revision.collection))?.supports?.includes("revisions") ?? false)) {
3534
+ const result = await handleRevisionRestore(this.db, revisionId, callerUserId);
3535
+ if (result.success) await this.refreshContentUsageAfterSuccessfulWrite(revision.collection, [revision.entryId]);
3536
+ return this.hydrateDraftData(result);
3537
+ }
3538
+ try {
3539
+ const contentRepo = new ContentRepository(this.db);
3540
+ const restoredData = { ...revision.data };
3541
+ delete restoredData[STAGED_REFERENCES_BASELINE_KEY];
3542
+ const newDraftId = await contentRepo.restoreDraftRevision(revision.collection, revision.entryId, restoredData, callerUserId);
3543
+ if (!newDraftId) return {
3544
+ success: false,
3545
+ error: {
3546
+ code: "NOT_FOUND",
3547
+ message: `Content item not found: ${revision.entryId}`
3548
+ }
3549
+ };
3550
+ after(async () => {
3551
+ try {
3552
+ await revisionRepo.pruneQueuedEntry(revision.collection, revision.entryId, newDraftId, 50);
3553
+ } catch (error) {
3554
+ console.error(`[revisions] Failed to prune revisions for ${revision.collection}/${revision.entryId}:`, error);
3555
+ }
3556
+ });
3557
+ const refetched = await handleContentGet(this.db, revision.collection, revision.entryId);
3558
+ const hydrated = await this.hydrateDraftData(refetched);
3559
+ if (hydrated.success) await this.refreshContentUsageAfterSuccessfulWrite(revision.collection, [revision.entryId]);
3560
+ return hydrated;
3561
+ } catch (error) {
3562
+ if (error instanceof ContentMutationConflictError) return {
3563
+ success: false,
3564
+ error: {
3565
+ code: "CONFLICT",
3566
+ message: error.message
3567
+ }
3568
+ };
3569
+ console.error("[emdash] revision restore failed:", error);
3570
+ return {
3571
+ success: false,
3572
+ error: {
3573
+ code: "REVISION_RESTORE_ERROR",
3574
+ message: "Failed to restore revision"
3575
+ }
3576
+ };
3577
+ }
3578
+ }
3579
+ async findSyncedSiblingsForUsageRefresh(collection, contentId, translationGroup, data, options = {}) {
3580
+ try {
3581
+ return await findNonTranslatableSiblingContentIds(this.db, collection, contentId, translationGroup, data, options);
3582
+ } catch (error) {
3583
+ console.error(`[media-usage] Failed to discover synced i18n siblings for ${collection}/${contentId}:`, error);
3584
+ try {
3585
+ await markContentMediaUsageCollectionStale(this.db, collection, "CONTENT_USAGE_REFRESH_ERROR");
3586
+ } catch (staleError) {
3587
+ console.error(`[media-usage] Failed to mark ${collection} stale:`, staleError);
3588
+ }
3589
+ return [];
3590
+ }
3591
+ }
3592
+ async refreshContentUsageAfterSuccessfulWrite(collection, contentIds) {
3593
+ for (const contentId of new Set(contentIds)) try {
3594
+ if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") continue;
3595
+ await refreshContentMediaUsageAfterWrite(this.db, collection, contentId);
3596
+ } catch (error) {
3597
+ console.error(`[media-usage] Failed after content write ${collection}/${contentId}:`, error);
3598
+ continue;
3599
+ }
3600
+ }
3601
+ async deleteContentUsageAfterSuccessfulPermanentDelete(collection, contentId) {
3602
+ try {
3603
+ if ((await processMediaUsageWorkAfterWrite(this.db, collection, contentId)).outcome !== "inactive") return;
3604
+ const result = await deleteContentMediaUsage(this.db, collection, contentId);
3605
+ if (!result.success) console.error(`[media-usage] Usage delete for ${collection}/${contentId} finished with ${result.errorCode}`);
3606
+ } catch (error) {
3607
+ console.error(`[media-usage] Failed after permanent content delete ${collection}/${contentId}:`, error);
3608
+ }
3609
+ }
3610
+ getSandboxedAdminDefinition(pluginId) {
3611
+ const entry = this.sandboxedPluginEntries.find((candidate) => candidate.id === pluginId);
3612
+ if (entry) {
3613
+ const pages = (entry.adminPages ?? []).map((page) => normalizePluginPagePath(page.path));
3614
+ const imageHosts = allowedBrowserImageHosts(entry.capabilities, entry.allowedHosts);
3615
+ return {
3616
+ pages,
3617
+ widgets: (entry.adminWidgets ?? []).map((widget) => widget.id),
3618
+ policy: {
3619
+ pluginPagePaths: pages,
3620
+ allowedImageHosts: imageHosts
3621
+ }
3622
+ };
3623
+ }
3624
+ const manifest = marketplaceManifestCache.get(pluginId);
3625
+ if (!manifest) return null;
3626
+ const pages = (manifest.admin?.pages ?? []).map((page) => normalizePluginPagePath(page.path));
3627
+ const imageHosts = allowedBrowserImageHosts(manifest.capabilities ?? [], manifest.allowedHosts ?? []);
3628
+ return {
3629
+ pages,
3630
+ widgets: (manifest.admin?.widgets ?? []).map((widget) => widget.id),
3631
+ policy: {
3632
+ pluginPagePaths: pages,
3633
+ allowedImageHosts: imageHosts
3634
+ }
3635
+ };
3636
+ }
3637
+ resolvePluginUiContext(definition, body, request) {
3638
+ if (typeof body !== "object" || body === null || !("page" in body)) return {};
3639
+ const page = body.page;
3640
+ if (typeof page !== "string") return { error: {
3641
+ code: "INVALID_PLUGIN_UI_CONTEXT",
3642
+ message: "Plugin UI page must be a string"
3643
+ } };
3644
+ let surface;
3645
+ if (page.startsWith("widget:")) {
3646
+ if (!definition.widgets.includes(page.slice(7))) return { error: {
3647
+ code: "INVALID_PLUGIN_UI_CONTEXT",
3648
+ message: "Plugin dashboard widget is not declared"
3649
+ } };
3650
+ surface = "dashboard-widget";
3651
+ } else {
3652
+ if (!definition.pages.includes(normalizePluginPagePath(page))) return { error: {
3653
+ code: "INVALID_PLUGIN_UI_CONTEXT",
3654
+ message: "Plugin admin page is not declared"
3655
+ } };
3656
+ surface = "admin-page";
3657
+ }
3658
+ const locale = resolveLocale(request);
3659
+ return { context: {
3660
+ surface,
3661
+ locale,
3662
+ direction: getLocaleDir(locale)
3663
+ } };
3664
+ }
3665
+ /**
3666
+ * UI context for a configured plugin's Block Kit page or widget. An
3667
+ * undeclared surface yields no context and no error: configured plugins
3668
+ * are not held to their declarations, so rejecting here would break
3669
+ * configured plugins that serve undeclared pages.
3670
+ */
3671
+ resolveTrustedUiContext(plugin, routeKey, body, request) {
3672
+ if (routeKey !== "admin") return void 0;
3673
+ const surfaces = {
3674
+ pages: (plugin.admin.pages ?? []).map((page) => normalizePluginPagePath(page.path)),
3675
+ widgets: (plugin.admin.widgets ?? []).map((widget) => widget.id)
3676
+ };
3677
+ return this.resolvePluginUiContext(surfaces, body, request).context;
3678
+ }
3679
+ validateSandboxedAdminResponse(pluginId, definition, result) {
3680
+ if (!result.success) return result;
3681
+ const validation = validateBlockResponse(result.data, definition.policy);
3682
+ if (validation.valid) return result;
3683
+ console.error(`EmDash: Sandboxed plugin ${pluginId} returned invalid Block Kit content:`, validation.errors);
3684
+ return {
3685
+ success: false,
3686
+ status: 502,
3687
+ error: {
3688
+ code: "INVALID_BLOCK_RESPONSE",
3689
+ message: "Plugin returned invalid Block Kit content"
3690
+ }
3691
+ };
3692
+ }
3693
+ getPluginEditorExtension(pluginId, kind, extensionId, collection) {
3694
+ if (!this.isPluginEnabled(pluginId)) return null;
3695
+ let panels;
3696
+ let actions;
3697
+ let pages = [];
3698
+ let capabilities = [];
3699
+ let allowedHosts = [];
3700
+ const configured = this.configuredPlugins.find((plugin) => plugin.id === pluginId);
3701
+ if (configured) {
3702
+ if (configured.admin.entry) return null;
3703
+ panels = configured.admin.editorPanels;
3704
+ actions = configured.admin.editorActions;
3705
+ pages = (configured.admin.pages ?? []).map((page) => page.path);
3706
+ capabilities = configured.capabilities;
3707
+ allowedHosts = configured.allowedHosts;
3708
+ } else {
3709
+ const entry = this.sandboxedPluginEntries.find((candidate) => candidate.id === pluginId);
3710
+ if (entry) {
3711
+ panels = entry.editorPanels;
3712
+ actions = entry.editorActions;
3713
+ pages = (entry.adminPages ?? []).map((page) => page.path);
3714
+ capabilities = entry.capabilities;
3715
+ allowedHosts = entry.allowedHosts;
3716
+ } else {
3717
+ const manifest = marketplaceManifestCache.get(pluginId);
3718
+ if (!manifest) return null;
3719
+ panels = manifest.admin?.editorPanels;
3720
+ actions = manifest.admin?.editorActions;
3721
+ pages = (manifest.admin?.pages ?? []).map((page) => page.path);
3722
+ capabilities = manifest.capabilities ?? [];
3723
+ allowedHosts = manifest.allowedHosts ?? [];
3724
+ }
3725
+ }
3726
+ const normalizedCapabilities = normalizePluginCapabilities(capabilities);
3727
+ const policy = {
3728
+ pluginPagePaths: pages,
3729
+ allowedImageHosts: allowedBrowserImageHosts(capabilities, allowedHosts)
3730
+ };
3731
+ if (kind === "panel") {
3732
+ const matches = panels?.filter((panel) => panel.id === extensionId) ?? [];
3733
+ const extension = matches[0];
3734
+ if (matches.length !== 1 || !extension || extension.collections && !extension.collections.includes(collection)) return null;
3735
+ return {
3736
+ kind,
3737
+ extension,
3738
+ policy,
3739
+ capabilities: normalizedCapabilities
3740
+ };
3741
+ }
3742
+ const matches = actions?.filter((action) => action.id === extensionId) ?? [];
3743
+ const extension = matches[0];
3744
+ if (matches.length !== 1 || !extension || extension.collections && !extension.collections.includes(collection) || extension.style === "danger" && !extension.confirm) return null;
3745
+ return {
3746
+ kind,
3747
+ extension,
3748
+ policy,
3749
+ capabilities: normalizedCapabilities
3750
+ };
3751
+ }
3752
+ async getPluginEditorDraftSchema(collection) {
3753
+ return this.schemaRegistry.getCollectionWithFields(collection);
3754
+ }
3755
+ validatePluginEditorExtensionResponse(pluginId, dispatch, result) {
3756
+ if (!result.success) return result;
3757
+ const validation = dispatch.kind === "panel" ? validateBlockResponse(result.data, dispatch.policy) : validateContentEditorActionResponse(result.data, dispatch.policy);
3758
+ if (validation.valid) return result;
3759
+ console.error(`EmDash: Plugin ${pluginId} returned an invalid content editor ${dispatch.kind} response:`, validation.errors);
3760
+ return {
3761
+ success: false,
3762
+ status: 502,
3763
+ error: {
3764
+ code: dispatch.kind === "panel" ? "INVALID_BLOCK_RESPONSE" : "INVALID_EDITOR_ACTION_RESPONSE",
3765
+ message: "Plugin returned an invalid editor extension response"
3766
+ }
3767
+ };
3768
+ }
3769
+ /**
3770
+ * Get route metadata for a plugin route without invoking the handler.
3771
+ * Used by the catch-all route to decide auth before dispatch.
3772
+ * Returns null if the plugin or route doesn't exist.
3773
+ */
3774
+ getPluginRouteMeta(pluginId, path) {
3775
+ if (!this.isPluginEnabled(pluginId)) return null;
3776
+ const routeKey = path.replace(LEADING_SLASH_PATTERN, "");
3777
+ const trustedPlugin = this.configuredPlugins.find((p) => p.id === pluginId);
3778
+ if (trustedPlugin) {
3779
+ const route = trustedPlugin.routes[routeKey];
3780
+ if (!route) return null;
3781
+ return buildRouteMeta(route);
3782
+ }
3783
+ const meta = sandboxedRouteMetaCache.get(pluginId);
3784
+ if (meta) {
3785
+ const routeMeta = meta.get(routeKey);
3786
+ if (routeMeta) return routeMeta;
3787
+ }
3788
+ if (routeKey === "admin") {
3789
+ const manifestMeta = marketplaceManifestCache.get(pluginId);
3790
+ if (manifestMeta?.admin?.pages?.length || manifestMeta?.admin?.widgets?.length) return { public: false };
3791
+ const entry = this.sandboxedPluginEntries.find((e) => e.id === pluginId);
3792
+ if (entry?.adminPages?.length || entry?.adminWidgets?.length) return { public: false };
3793
+ }
3794
+ if (this.findSandboxedPlugin(pluginId)) return { public: false };
3795
+ return null;
3796
+ }
3797
+ /**
3798
+ * Resolve the settings schema for a runtime-installed (marketplace or
3799
+ * registry) plugin from its cached manifest. Returns `{}` for a known
3800
+ * plugin without a schema and `null` for unknown plugins, matching the
3801
+ * contract of `getPluginSettingsSchema` for build-time plugins.
3802
+ */
3803
+ getRuntimePluginSettingsSchema(pluginId) {
3804
+ const meta = marketplaceManifestCache.get(pluginId);
3805
+ if (!meta) return null;
3806
+ return meta.admin?.settingsSchema ?? {};
3807
+ }
3808
+ async handlePluginApiRoute(pluginId, method, path, request, user, invalidateContentCache, editorDispatch) {
3809
+ if (!this.isPluginEnabled(pluginId)) return {
3810
+ success: false,
3811
+ error: {
3812
+ code: "NOT_FOUND",
3813
+ message: `Plugin not enabled: ${pluginId}`
3814
+ }
3815
+ };
3816
+ const normalizedMethod = method.toUpperCase();
3817
+ const routeMeta = this.getPluginRouteMeta(pluginId, path);
3818
+ if (routeMeta?.methods && !routeMeta.methods.some((allowed) => allowed === normalizedMethod)) return {
3819
+ success: false,
3820
+ status: 405,
3821
+ error: {
3822
+ code: "METHOD_NOT_ALLOWED",
3823
+ message: "Method not allowed"
3824
+ }
3825
+ };
3826
+ const caller = user ? toRouteCallerInfo(user) : void 0;
3827
+ let body;
3828
+ try {
3829
+ body = await parseRouteInput(request, routeMeta?.request);
3830
+ } catch (error) {
3831
+ if (error instanceof PluginRouteRequestError) return {
3832
+ success: false,
3833
+ status: error.status,
3834
+ error: {
3835
+ code: "INVALID_PLUGIN_REQUEST",
3836
+ message: error.message
3837
+ }
3838
+ };
3839
+ throw error;
3840
+ }
3841
+ const routeKey = path.replace(LEADING_SLASH_PATTERN, "");
3842
+ const adminDefinition = !editorDispatch && routeKey === "admin" ? this.getSandboxedAdminDefinition(pluginId) : null;
3843
+ const uiResult = adminDefinition ? this.resolvePluginUiContext(adminDefinition, body, request) : {};
3844
+ if (uiResult.error) return {
3845
+ success: false,
3846
+ status: 400,
3847
+ error: uiResult.error
3848
+ };
3849
+ const trustedPlugin = this.configuredPlugins.find((p) => p.id === pluginId);
3850
+ if (trustedPlugin && this.enabledPlugins.has(trustedPlugin.id)) {
3851
+ const routeRegistry = new PluginRouteRegistry({
3852
+ ...this.pipelineFactoryOptions,
3853
+ contentActions: this.contentActionsWithInvalidator(invalidateContentCache),
3854
+ emailPipeline: this.email ?? void 0,
3855
+ cronReschedule: () => this.cronScheduler?.reschedule(),
3856
+ trustedProxyHeaders: getTrustedProxyHeaders(this.config)
3857
+ });
3858
+ routeRegistry.register(trustedPlugin);
3859
+ const result = await routeRegistry.invoke(pluginId, routeKey, {
3860
+ request,
3861
+ body,
3862
+ user: caller,
3863
+ ui: editorDispatch?.ui ?? uiResult.context ?? this.resolveTrustedUiContext(trustedPlugin, routeKey, body, request)
3864
+ });
3865
+ return editorDispatch ? this.validatePluginEditorExtensionResponse(pluginId, editorDispatch, result) : adminDefinition ? this.validateSandboxedAdminResponse(pluginId, adminDefinition, result) : result;
3866
+ }
3867
+ const sandboxedPlugin = this.findSandboxedPlugin(pluginId);
3868
+ if (sandboxedPlugin) {
3869
+ const result = await this.handleSandboxedRoute(sandboxedPlugin, path, request, body, routeMeta ?? { public: false }, caller, editorDispatch?.ui ?? uiResult.context, invalidateContentCache);
3870
+ return editorDispatch ? this.validatePluginEditorExtensionResponse(pluginId, editorDispatch, result) : adminDefinition ? this.validateSandboxedAdminResponse(pluginId, adminDefinition, result) : result;
3871
+ }
3872
+ return {
3873
+ success: false,
3874
+ error: {
3875
+ code: "NOT_FOUND",
3876
+ message: `Plugin not found: ${pluginId}`
3877
+ }
3878
+ };
3879
+ }
3880
+ contentActionsWithInvalidator(invalidateContentCache) {
3881
+ const actions = this.pipelineFactoryOptions.contentActions;
3882
+ if (!actions || !invalidateContentCache) return actions;
3883
+ return {
3884
+ ...actions,
3885
+ begin: (pluginId, invocationId, invocationInvalidator) => actions.begin?.(pluginId, invocationId, invocationInvalidator ?? invalidateContentCache),
3886
+ publish: (pluginId, collection, id, options, invocationId) => actions.publish(pluginId, collection, id, options, invocationId, invalidateContentCache),
3887
+ unpublish: (pluginId, collection, id, options, invocationId) => actions.unpublish(pluginId, collection, id, options, invocationId, invalidateContentCache),
3888
+ schedule: (pluginId, collection, id, options, invocationId) => actions.schedule(pluginId, collection, id, options, invocationId, invalidateContentCache),
3889
+ unschedule: (pluginId, collection, id, options, invocationId) => actions.unschedule(pluginId, collection, id, options, invocationId, invalidateContentCache),
3890
+ restore: (pluginId, collection, id, options, invocationId) => actions.restore(pluginId, collection, id, options, invocationId, invalidateContentCache)
3891
+ };
3892
+ }
3893
+ async getPluginMcpTools(pluginId) {
3894
+ const tools = [];
3895
+ const seen = /* @__PURE__ */ new Set();
3896
+ for (const plugin of this.configuredPlugins) {
3897
+ if (pluginId && plugin.id !== pluginId) continue;
3898
+ for (const [name, tool] of Object.entries(plugin.mcp?.tools ?? {})) {
3899
+ const route = plugin.routes[tool.route];
3900
+ if (!route || route.public || route.response === "raw" || !isJsonPostRouteContract(route) || !route.permission || !Object.hasOwn(Permissions, route.permission)) continue;
3901
+ const key = `${plugin.id}__${name}`;
3902
+ if (seen.has(key)) continue;
3903
+ seen.add(key);
3904
+ tools.push({
3905
+ pluginId: plugin.id,
3906
+ name,
3907
+ mcpName: pluginToolName(plugin.id, name),
3908
+ description: tool.description,
3909
+ route: tool.route,
3910
+ permission: route.permission,
3911
+ destructive: tool.destructive ?? false,
3912
+ inputSchema: tool.input,
3913
+ outputSchema: tool.output
3914
+ });
3915
+ }
3916
+ }
3917
+ const addManifestTools = (id, mcp) => {
3918
+ if (pluginId && id !== pluginId) return;
3919
+ for (const tool of mcp?.tools ?? []) {
3920
+ const key = `${id}__${tool.name}`;
3921
+ const routeMeta = this.getPluginRouteMeta(id, tool.route);
3922
+ if (seen.has(key) || !routeMeta || routeMeta.public || routeMeta.response === "raw" || !isJsonPostRouteContract(routeMeta) || routeMeta.permission !== tool.permission || !Object.hasOwn(Permissions, tool.permission)) continue;
3923
+ seen.add(key);
3924
+ tools.push({
3925
+ pluginId: id,
3926
+ name: tool.name,
3927
+ mcpName: pluginToolName(id, tool.name),
3928
+ description: tool.description,
3929
+ route: tool.route,
3930
+ permission: tool.permission,
3931
+ destructive: tool.destructive,
3932
+ inputSchema: safeJsonSchemaToZod({ ...tool.inputSchema }, `${id}/${tool.name}`, "input") ?? z.record(z.string(), z.unknown()),
3933
+ outputSchema: tool.outputSchema ? safeJsonSchemaToZod({ ...tool.outputSchema }, `${id}/${tool.name}`, "output") : void 0
3934
+ });
3935
+ }
3936
+ };
3937
+ for (const entry of this.sandboxedPluginEntries) addManifestTools(entry.id, entry.mcp);
3938
+ for (const [id, manifest] of marketplaceManifestCache) addManifestTools(id, manifest.mcp);
3939
+ return tools;
3940
+ }
3941
+ async getEnabledPluginMcpTools() {
3942
+ const [tools, states] = await Promise.all([this.getPluginMcpTools(), new PluginStateRepository(this.db).getAll()]);
3943
+ const stateByPlugin = new Map(states.map((state) => [state.pluginId, state]));
3944
+ return tools.filter((tool) => {
3945
+ const state = stateByPlugin.get(tool.pluginId);
3946
+ if (!state?.mcpToolsEnabled || state.status !== "active" || !this.isPluginEnabled(tool.pluginId)) return false;
3947
+ return state.mcpToolsConsent === this.serializePluginMcpConsent(tools, tool.pluginId);
3948
+ });
3949
+ }
3950
+ serializePluginMcpConsent(tools, pluginId) {
3951
+ return JSON.stringify(tools.filter((tool) => tool.pluginId === pluginId).map((tool) => ({
3952
+ name: tool.name,
3953
+ description: tool.description,
3954
+ route: tool.route,
3955
+ permission: tool.permission,
3956
+ destructive: tool.destructive,
3957
+ inputSchema: z.toJSONSchema(tool.inputSchema, { target: "draft-7" }),
3958
+ ...tool.outputSchema ? { outputSchema: z.toJSONSchema(tool.outputSchema, { target: "draft-7" }) } : {}
3959
+ })).toSorted((a, b) => a.name.localeCompare(b.name)));
3960
+ }
3961
+ async handlePluginMcpTool(pluginId, toolName, route, input, actorId, request, caller, invalidateContentCache) {
3962
+ const requestMeta = extractRequestMeta(request, getTrustedProxyHeaders(this.config));
3963
+ const audit = new AuditRepository(this.db);
3964
+ const headers = new Headers(request.headers);
3965
+ headers.delete("content-length");
3966
+ headers.delete("content-encoding");
3967
+ const internalRequest = new Request(request.url, {
3968
+ method: "POST",
3969
+ headers,
3970
+ body: JSON.stringify(input)
3971
+ });
3972
+ const result = await this.handlePluginApiRoute(pluginId, "POST", route, internalRequest, caller, invalidateContentCache);
3973
+ await audit.log({
3974
+ actorId,
3975
+ actorIp: requestMeta.ip ?? void 0,
3976
+ action: "plugin_tool_invoke",
3977
+ resourceType: "plugin_mcp_tool",
3978
+ resourceId: `${pluginId}__${toolName}`,
3979
+ details: {
3980
+ pluginId,
3981
+ tool: toolName,
3982
+ route
3983
+ },
3984
+ status: result.success ? "success" : "failure"
3985
+ });
3986
+ return result;
3987
+ }
3988
+ async handlePluginMcpDenied(pluginId, toolName, route, actorId, request, reason) {
3989
+ const requestMeta = extractRequestMeta(request, getTrustedProxyHeaders(this.config));
3990
+ await new AuditRepository(this.db).log({
3991
+ actorId,
3992
+ actorIp: requestMeta.ip ?? void 0,
3993
+ action: "plugin_tool_invoke",
3994
+ resourceType: "plugin_mcp_tool",
3995
+ resourceId: `${pluginId}__${toolName}`,
3996
+ details: {
3997
+ pluginId,
3998
+ tool: toolName,
3999
+ route,
4000
+ reason
4001
+ },
4002
+ status: "denied"
4003
+ });
4004
+ }
4005
+ findSandboxedPlugin(pluginId) {
4006
+ for (const [key, plugin] of this.sandboxedPlugins) if (key.startsWith(pluginId + ":")) return plugin;
4007
+ }
4008
+ /**
4009
+ * Drop incoming keys that have no matching collection field when the entry
4010
+ * already stores them in its live data or current draft revision.
4011
+ *
4012
+ * Draft revisions keep the full `data` JSON, so deleting a field can strand
4013
+ * the old value; this lets a read-then-write save succeed without allowing
4014
+ * genuinely unknown keys.
4015
+ */
4016
+ async dropUnknownKeysAlreadyStored(data, existing, knownFieldSlugs) {
4017
+ if (!existing) return data;
4018
+ let stored = existing.data ?? {};
4019
+ if (existing.draftRevisionId) {
4020
+ const draft = await new RevisionRepository(this.db).findById(existing.draftRevisionId).catch(() => null);
4021
+ if (draft?.data) stored = {
4022
+ ...stored,
4023
+ ...draft.data
4024
+ };
4025
+ }
4026
+ const stale = staleStoredKeys(data, stored, knownFieldSlugs);
4027
+ if (stale.length === 0) return data;
4028
+ const result = { ...data };
4029
+ for (const key of stale) delete result[key];
4030
+ return result;
4031
+ }
4032
+ /**
4033
+ * Normalize field values in content data before validation.
4034
+ * Turns a blank string in an array-valued field into `null`, and fills
4035
+ * missing image/file dimensions, storageKey, mimeType, and filename from providers.
4036
+ */
4037
+ async normalizeFieldValues(collection, data, preloaded, preloadedBlockTypes, includeBlocks = true) {
4038
+ let collectionInfo = preloaded;
4039
+ if (collectionInfo === void 0) try {
4040
+ collectionInfo = await this.schemaRegistry.getCollectionWithFields(collection);
4041
+ } catch {
4042
+ return data;
4043
+ }
4044
+ if (!collectionInfo?.fields) return data;
4045
+ const result = { ...data };
4046
+ for (const field of collectionInfo.fields) {
4047
+ const value = result[field.slug];
4048
+ if (ARRAY_FIELD_TYPES.has(field.type) && typeof value === "string" && !value.trim()) result[field.slug] = null;
4049
+ }
4050
+ const imageFields = collectionInfo.fields.filter((f) => f.type === "image" || f.type === "file");
4051
+ const repeaterFields = collectionInfo.fields.filter((f) => f.type === "repeater" && Array.isArray(f.validation?.subFields));
4052
+ const blockFields = includeBlocks ? collectionInfo.fields.filter((field) => field.type === "blocks") : [];
4053
+ if (imageFields.length === 0 && repeaterFields.length === 0 && blockFields.length === 0) return result;
4054
+ const getProvider = (id) => this.getMediaProvider(id);
4055
+ for (const field of imageFields) {
4056
+ const value = result[field.slug];
4057
+ if (value == null) continue;
4058
+ try {
4059
+ const normalized = field.type === "image" ? await normalizeImageValue(value, getProvider) : await normalizeMediaValue(value, getProvider);
4060
+ if (normalized) result[field.slug] = normalized;
4061
+ } catch {}
4062
+ }
4063
+ for (const field of repeaterFields) {
4064
+ const value = result[field.slug];
4065
+ if (!Array.isArray(value)) continue;
4066
+ const mediaSubFieldSlugs = (field.validation?.subFields ?? []).filter((sub) => sub.type === "image").map((sub) => sub.slug);
4067
+ if (mediaSubFieldSlugs.length === 0) continue;
4068
+ const items = value;
4069
+ result[field.slug] = await Promise.all(items.map(async (item) => {
4070
+ if (!isRecord(item)) return item;
4071
+ const normalizedItem = { ...item };
4072
+ for (const slug of mediaSubFieldSlugs) {
4073
+ const subValue = normalizedItem[slug];
4074
+ if (subValue == null) continue;
4075
+ try {
4076
+ const normalized = await normalizeImageValue(subValue, getProvider);
4077
+ if (normalized) normalizedItem[slug] = normalized;
4078
+ } catch {}
4079
+ }
4080
+ return normalizedItem;
4081
+ }));
4082
+ }
4083
+ if (blockFields.length > 0) {
4084
+ const blockTypes = preloadedBlockTypes ?? await resolveBlockTypes(this.db);
4085
+ for (const field of blockFields) {
4086
+ const value = result[field.slug];
4087
+ if (!Array.isArray(value)) continue;
4088
+ result[field.slug] = await Promise.all(value.map(async (block) => {
4089
+ if (!isRecord(block) || typeof block._type !== "string") return block;
4090
+ const version = blockTypes.get(block._type)?.versions.find((candidate) => candidate.version === block._version);
4091
+ if (!version || version.unsupportedTypes?.length) return block;
4092
+ const normalizedBlock = { ...block };
4093
+ for (const nestedField of version.fields) {
4094
+ const nestedValue = normalizedBlock[nestedField.slug];
4095
+ if (nestedValue == null) continue;
4096
+ try {
4097
+ if (nestedField.type === "image") {
4098
+ const normalized = await normalizeImageValue(nestedValue, getProvider);
4099
+ if (normalized) normalizedBlock[nestedField.slug] = normalized;
4100
+ } else if (nestedField.type === "file") {
4101
+ const normalized = await normalizeMediaValue(nestedValue, getProvider);
4102
+ if (normalized) normalizedBlock[nestedField.slug] = normalized;
4103
+ } else if (nestedField.type === "repeater" && Array.isArray(nestedValue)) {
4104
+ const imageSlugs = (nestedField.validation?.subFields ?? []).filter((subField) => subField.type === "image").map((subField) => subField.slug);
4105
+ normalizedBlock[nestedField.slug] = await Promise.all(nestedValue.map(async (item) => {
4106
+ if (!isRecord(item)) return item;
4107
+ const normalizedItem = { ...item };
4108
+ for (const slug of imageSlugs) try {
4109
+ const normalized = await normalizeImageValue(normalizedItem[slug], getProvider);
4110
+ if (normalized) normalizedItem[slug] = normalized;
4111
+ } catch {
4112
+ continue;
4113
+ }
4114
+ return normalizedItem;
4115
+ }));
4116
+ }
4117
+ } catch {
4118
+ continue;
4119
+ }
4120
+ }
4121
+ return normalizedBlock;
4122
+ }));
4123
+ }
4124
+ }
4125
+ return result;
4126
+ }
4127
+ runAfterSaveHooks(content, collection, isNew, actor, excludePluginId, details) {
4128
+ after(async () => {
4129
+ if (this.hooks.hasHooks("content:afterSave")) try {
4130
+ await this.hooks.runContentAfterSave(content, collection, isNew, actor, excludePluginId, details);
4131
+ } catch (err) {
4132
+ console.error("EmDash afterSave hook error:", err);
4133
+ }
4134
+ });
4135
+ }
4136
+ runAfterDeleteHooks(id, collection, permanent) {
4137
+ after(async () => {
4138
+ if (this.hooks.hasHooks("content:afterDelete")) try {
4139
+ await this.hooks.runContentAfterDelete(id, collection, permanent);
4140
+ } catch (err) {
4141
+ console.error("EmDash afterDelete hook error:", err);
4142
+ }
4143
+ });
4144
+ }
4145
+ runMediaAfterUploadHook(mediaItem) {
4146
+ after(async () => {
4147
+ try {
4148
+ await this.hooks.runMediaAfterUpload(mediaItem);
4149
+ } catch (err) {
4150
+ console.error("EmDash afterUpload hook error:", err);
4151
+ }
4152
+ });
4153
+ }
4154
+ runDeferredContentHook(name, content, collection, afterPluginId, pluginInvocationId) {
4155
+ const label = name.slice(8);
4156
+ const invoke = async () => {
4157
+ if (this.hooks.hasHooks(name)) try {
4158
+ switch (name) {
4159
+ case "content:afterPublish":
4160
+ await this.hooks.runContentAfterPublish(content, collection);
4161
+ break;
4162
+ case "content:afterUnpublish":
4163
+ await this.hooks.runContentAfterUnpublish(content, collection);
4164
+ break;
4165
+ case "content:afterRestore":
4166
+ await this.hooks.runContentAfterRestore(content, collection);
4167
+ break;
4168
+ case "content:afterSchedule":
4169
+ await this.hooks.runContentAfterSchedule(content, collection);
4170
+ break;
4171
+ case "content:afterUnschedule":
4172
+ await this.hooks.runContentAfterUnschedule(content, collection);
4173
+ break;
4174
+ }
4175
+ } catch (err) {
4176
+ console.error(`EmDash ${label} hook error:`, err);
4177
+ }
4178
+ };
4179
+ const contentId = typeof content.id === "string" ? content.id : void 0;
4180
+ const guardKey = afterPluginId && contentId ? this.pluginContentActionKey(collection, contentId) : void 0;
4181
+ const invokeWithGuard = async () => {
4182
+ if (guardKey) this.retainPluginContentAction(guardKey);
4183
+ try {
4184
+ await invoke();
4185
+ } finally {
4186
+ if (guardKey) this.releasePluginContentAction(guardKey);
4187
+ }
4188
+ };
4189
+ if (afterPluginId && pluginInvocationId && this.findSandboxedPlugin(afterPluginId)) {
4190
+ const key = this.pluginInvocationKey(afterPluginId, pluginInvocationId);
4191
+ if (!this.activePluginInvocations.has(key) || this.releasedPluginInvocations.has(key)) {
4192
+ after(invokeWithGuard);
4193
+ return;
4194
+ }
4195
+ const pending = this.pendingPluginAfterHooks.get(key) ?? [];
4196
+ pending.push(invokeWithGuard);
4197
+ this.pendingPluginAfterHooks.set(key, pending);
4198
+ return;
4199
+ }
4200
+ after(invokeWithGuard);
4201
+ }
4202
+ pluginInvocationKey(pluginId, invocationId) {
4203
+ return JSON.stringify([pluginId, invocationId]);
4204
+ }
4205
+ beginPluginInvocation(pluginId, invocationId, invalidateContentCache) {
4206
+ const key = this.pluginInvocationKey(pluginId, invocationId);
4207
+ this.pendingPluginAfterHooks.delete(key);
4208
+ this.releasedPluginInvocations.delete(key);
4209
+ if (invalidateContentCache) this.pluginInvocationCacheInvalidators.set(key, invalidateContentCache);
4210
+ else this.pluginInvocationCacheInvalidators.delete(key);
4211
+ this.activePluginInvocations.add(key);
4212
+ }
4213
+ flushPluginAfterHooks(pluginId, invocationId, final = true) {
4214
+ if (!invocationId) return Promise.resolve();
4215
+ const key = this.pluginInvocationKey(pluginId, invocationId);
4216
+ this.releasedPluginInvocations.add(key);
4217
+ const pending = this.pendingPluginAfterHooks.get(key) ?? [];
4218
+ this.pendingPluginAfterHooks.delete(key);
4219
+ for (const invoke of pending) after(invoke);
4220
+ if (final) {
4221
+ this.releasedPluginInvocations.delete(key);
4222
+ this.activePluginInvocations.delete(key);
4223
+ this.pluginInvocationCacheInvalidators.delete(key);
4224
+ } else {
4225
+ const timer = setTimeout(() => {
4226
+ this.releasedPluginInvocations.delete(key);
4227
+ this.activePluginInvocations.delete(key);
4228
+ this.pluginInvocationCacheInvalidators.delete(key);
4229
+ }, PLUGIN_INVOCATION_RELEASE_GRACE_MS);
4230
+ if (typeof timer === "object") timer.unref();
4231
+ }
4232
+ return Promise.resolve();
4233
+ }
4234
+ runAfterPublishHooks(content, collection, afterPluginId, pluginInvocationId) {
4235
+ this.runDeferredContentHook("content:afterPublish", content, collection, afterPluginId, pluginInvocationId);
4236
+ }
4237
+ runAfterUnpublishHooks(content, collection, afterPluginId, pluginInvocationId) {
4238
+ this.runDeferredContentHook("content:afterUnpublish", content, collection, afterPluginId, pluginInvocationId);
4239
+ }
4240
+ runAfterRestoreHooks(content, collection, afterPluginId, pluginInvocationId) {
4241
+ this.runDeferredContentHook("content:afterRestore", content, collection, afterPluginId, pluginInvocationId);
4242
+ }
4243
+ runAfterScheduleHooks(content, collection, afterPluginId, pluginInvocationId) {
4244
+ this.runDeferredContentHook("content:afterSchedule", content, collection, afterPluginId, pluginInvocationId);
4245
+ }
4246
+ runAfterUnscheduleHooks(content, collection, afterPluginId, pluginInvocationId) {
4247
+ this.runDeferredContentHook("content:afterUnschedule", content, collection, afterPluginId, pluginInvocationId);
4248
+ }
4249
+ async handleSandboxedRoute(plugin, path, request, body, routeMeta, user, ui, invalidateContentCache) {
4250
+ const routeName = path.replace(LEADING_SLASH_PATTERN, "");
4251
+ try {
4252
+ const declaredHeaders = routeMeta.request ? routeMeta.request.headers ?? [] : void 0;
4253
+ const headers = sanitizeHeadersForSandbox(request.headers, declaredHeaders);
4254
+ const meta = extractRequestMeta(request, this.config);
4255
+ return {
4256
+ success: true,
4257
+ data: await plugin.invokeRoute(routeName, body, {
4258
+ url: request.url,
4259
+ method: request.method,
4260
+ headers,
4261
+ meta,
4262
+ user,
4263
+ ui
4264
+ }, { invalidateContentCache })
4265
+ };
4266
+ } catch (error) {
4267
+ if (error instanceof PluginRouteRequestError) return {
4268
+ success: false,
4269
+ status: error.status,
4270
+ error: {
4271
+ code: "INVALID_PLUGIN_REQUEST",
4272
+ message: error.message
4273
+ }
4274
+ };
4275
+ console.error(`EmDash: Sandboxed plugin route error:`, error);
4276
+ const sandboxRouteError = getSandboxRouteErrorDetails(error);
4277
+ if (sandboxRouteError) return {
4278
+ success: false,
4279
+ status: sandboxRouteError.status,
4280
+ error: {
4281
+ code: sandboxRouteError.code,
4282
+ message: sandboxRouteError.message
4283
+ }
4284
+ };
4285
+ return {
4286
+ success: false,
4287
+ error: {
4288
+ code: "ROUTE_ERROR",
4289
+ message: error instanceof Error ? error.message : String(error)
4290
+ }
4291
+ };
4292
+ }
4293
+ }
4294
+ /**
4295
+ * Cache for page contributions. Uses a WeakMap keyed on the PublicPageContext
4296
+ * object so results are collected once per page context per request, even when
4297
+ * multiple render components (EmDashHead, EmDashBodyStart, EmDashBodyEnd)
4298
+ * request contributions from the same page.
4299
+ */
4300
+ pageContributionCache = /* @__PURE__ */ new WeakMap();
4301
+ /**
4302
+ * Collect all page contributions (metadata + fragments) in a single pass.
4303
+ * Results are cached by page context object identity.
4304
+ */
4305
+ async collectPageContributions(page) {
4306
+ const cached = this.pageContributionCache.get(page);
4307
+ if (cached) return cached;
4308
+ const promise = this.doCollectPageContributions(page);
4309
+ this.pageContributionCache.set(page, promise);
4310
+ return promise;
4311
+ }
4312
+ async doCollectPageContributions(page) {
4313
+ const metadata = [];
4314
+ const fragments = [];
4315
+ if (this.hooks.hasHooks("page:metadata")) {
4316
+ const results = await this.hooks.runPageMetadata({ page });
4317
+ for (const r of results) metadata.push(...r.contributions);
4318
+ }
4319
+ if (this.hooks.hasHooks("page:fragments")) {
4320
+ const results = await this.hooks.runPageFragments({ page });
4321
+ for (const r of results) fragments.push(...r.contributions);
4322
+ }
4323
+ return {
4324
+ metadata,
4325
+ fragments
4326
+ };
4327
+ }
4328
+ /**
4329
+ * Collect page metadata contributions from trusted and sandboxed plugins.
4330
+ * Delegates to the single-pass collector and returns the metadata portion.
4331
+ */
4332
+ async collectPageMetadata(page) {
4333
+ const { metadata } = await this.collectPageContributions(page);
4334
+ return metadata;
4335
+ }
4336
+ /**
4337
+ * Collect page fragment contributions from trusted plugins only.
4338
+ * Delegates to the single-pass collector and returns the fragments portion.
4339
+ */
4340
+ async collectPageFragments(page) {
4341
+ const { fragments } = await this.collectPageContributions(page);
4342
+ return fragments;
4343
+ }
4344
+ isPluginEnabled(pluginId) {
4345
+ const status = this.pluginStates.get(pluginId);
4346
+ return status === void 0 || status === "active";
4347
+ }
4348
+ };
4349
+ /**
4350
+ * Normalize an image field value together with its `darkVariant`. The media
4351
+ * normalizer only keeps the media item's own keys, so the variant is normalized
4352
+ * separately and reattached.
4353
+ */
4354
+ async function normalizeImageValue(value, getProvider) {
4355
+ const primary = await normalizePrimaryImageValue(value, getProvider);
4356
+ if (!primary || !isRecord(value) || value.darkVariant == null) return primary;
4357
+ const darkVariant = await normalizeMediaValue(value.darkVariant, getProvider);
4358
+ return darkVariant ? {
4359
+ ...primary,
4360
+ darkVariant
4361
+ } : primary;
4362
+ }
4363
+ /**
4364
+ * Normalize the primary image of an image field value.
4365
+ *
4366
+ * A legacy string URL that the admin upgraded to `{ id: "", src: url }` so it
4367
+ * can carry a dark variant still has to normalize as a string: as an object it
4368
+ * counts as local media, which strips `src` and leaves nothing behind. The
4369
+ * upgrade outlives the variant — an editor can add one and remove it again —
4370
+ * so the shape decides, not the presence of `darkVariant`.
4371
+ */
4372
+ async function normalizePrimaryImageValue(value, getProvider) {
4373
+ if (isRecord(value) && !value.id && typeof value.src === "string" && !value.provider) return normalizeMediaValue(value.src, getProvider);
4374
+ return normalizeMediaValue(value, getProvider);
4375
+ }
4376
+
4377
+ //#endregion
4378
+ export { EmDashRuntime as n, DB_INIT_DEADLINE_MS as t };
4379
+ //# sourceMappingURL=emdash-runtime-DVi7vtXC.mjs.map