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