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