emdash 1.0.1 → 1.2.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 (1439) hide show
  1. package/dist/action-token-BdWad2YG.mjs +26 -0
  2. package/dist/action-token-BdWad2YG.mjs.map +1 -0
  3. package/dist/activation-xomsCMJg.mjs +387 -0
  4. package/dist/activation-xomsCMJg.mjs.map +1 -0
  5. package/dist/adapters-B4-nbElt.d.mts +173 -0
  6. package/dist/adapters-B4-nbElt.d.mts.map +1 -0
  7. package/dist/allowed-origins-CLpHGGlc.mjs +116 -0
  8. package/dist/allowed-origins-CLpHGGlc.mjs.map +1 -0
  9. package/dist/api/route-utils.d.mts +4 -4
  10. package/dist/api/route-utils.d.mts.map +1 -1
  11. package/dist/api/route-utils.mjs +49 -48
  12. package/dist/api/route-utils.mjs.map +1 -1
  13. package/dist/api/schemas/index.d.mts +2 -2
  14. package/dist/api/schemas/index.mjs +10 -10
  15. package/dist/api-DBw_IWoi.mjs +5024 -0
  16. package/dist/api-DBw_IWoi.mjs.map +1 -0
  17. package/dist/api-tokens-B0ZipcZ-.mjs +173 -0
  18. package/dist/api-tokens-B0ZipcZ-.mjs.map +1 -0
  19. package/dist/apply-Cx48saFC.mjs +1680 -0
  20. package/dist/apply-Cx48saFC.mjs.map +1 -0
  21. package/dist/approvals-BXgtXA2s.mjs +460 -0
  22. package/dist/approvals-BXgtXA2s.mjs.map +1 -0
  23. package/dist/artifact-fetch-DsRczRGG.mjs +659 -0
  24. package/dist/artifact-fetch-DsRczRGG.mjs.map +1 -0
  25. package/dist/artifacts-D-O-qj9C.mjs +307 -0
  26. package/dist/artifacts-D-O-qj9C.mjs.map +1 -0
  27. package/dist/astro/image-endpoint.d.mts.map +1 -1
  28. package/dist/astro/image-endpoint.mjs +26 -6
  29. package/dist/astro/image-endpoint.mjs.map +1 -1
  30. package/dist/astro/index.d.mts +14 -14
  31. package/dist/astro/index.d.mts.map +1 -1
  32. package/dist/astro/index.mjs +253 -17
  33. package/dist/astro/index.mjs.map +1 -1
  34. package/dist/astro/middleware/auth.d.mts +12 -12
  35. package/dist/astro/middleware/auth.d.mts.map +1 -1
  36. package/dist/astro/middleware/auth.mjs +18 -13
  37. package/dist/astro/middleware/auth.mjs.map +1 -1
  38. package/dist/astro/middleware/media-usage-write-fence.mjs +6 -6
  39. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  40. package/dist/astro/middleware/redirect.mjs +34 -32
  41. package/dist/astro/middleware/redirect.mjs.map +1 -1
  42. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  43. package/dist/astro/middleware/request-context.mjs +99 -60
  44. package/dist/astro/middleware/request-context.mjs.map +1 -1
  45. package/dist/astro/middleware/setup.mjs +1 -1
  46. package/dist/astro/middleware.d.mts +12 -12
  47. package/dist/astro/middleware.d.mts.map +1 -1
  48. package/dist/astro/middleware.mjs +233 -128
  49. package/dist/astro/middleware.mjs.map +1 -1
  50. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +15 -15
  51. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +15 -15
  52. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +8 -7
  53. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs.map +1 -1
  54. package/dist/astro/routes/api/admin/api-tokens/index.mjs +9 -8
  55. package/dist/astro/routes/api/admin/api-tokens/index.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +7 -7
  57. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +17 -17
  58. package/dist/astro/routes/api/admin/byline-fields/index.mjs +17 -17
  59. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +17 -17
  60. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +22 -22
  61. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +22 -23
  62. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  63. package/dist/astro/routes/api/admin/bylines/index.mjs +23 -23
  64. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  65. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +19 -19
  66. package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -8
  67. package/dist/astro/routes/api/admin/comments/bulk.mjs +18 -18
  68. package/dist/astro/routes/api/admin/comments/counts.mjs +8 -8
  69. package/dist/astro/routes/api/admin/comments/index.mjs +18 -18
  70. package/dist/astro/routes/api/admin/core-update.mjs +8 -8
  71. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +7 -7
  72. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +6 -6
  73. package/dist/astro/routes/api/admin/media-usage/activation.mjs +21 -21
  74. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +22 -22
  75. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +22 -22
  76. package/dist/astro/routes/api/admin/media-usage/progress.mjs +23 -23
  77. package/dist/astro/routes/api/admin/media-usage/repair.mjs +29 -29
  78. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +22 -22
  79. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +22 -22
  80. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +7 -7
  81. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +7 -7
  82. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +9 -9
  83. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +9 -9
  84. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +73 -72
  85. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +8 -8
  87. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +71 -70
  88. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +72 -71
  90. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  91. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +82 -81
  92. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/index.mjs +72 -71
  94. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +8 -8
  96. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +71 -70
  97. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +81 -80
  99. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +71 -70
  101. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  102. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +72 -71
  103. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  104. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +83 -82
  105. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +10 -10
  107. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +74 -73
  108. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  109. package/dist/astro/routes/api/admin/plugins/registry/publisher-handle.mjs +8 -8
  110. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +72 -71
  111. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
  112. package/dist/astro/routes/api/admin/plugins/updates.mjs +71 -70
  113. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  114. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +6 -6
  115. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +71 -70
  116. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  117. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +8 -8
  118. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +71 -70
  119. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  120. package/dist/astro/routes/api/admin/transfer/_...path_.mjs +5 -5
  121. package/dist/astro/routes/api/admin/transfer/approvals/_id_/approve.mjs +39 -39
  122. package/dist/astro/routes/api/admin/transfer/approvals/_id_/deny.mjs +39 -39
  123. package/dist/astro/routes/api/admin/transfer/approvals/index.mjs +41 -41
  124. package/dist/astro/routes/api/admin/transfer/capabilities.mjs +41 -41
  125. package/dist/astro/routes/api/admin/transfer/exports/_id_/advance.mjs +41 -41
  126. package/dist/astro/routes/api/admin/transfer/exports/_id_/archive.mjs +42 -42
  127. package/dist/astro/routes/api/admin/transfer/exports/_id_/files/_...path_.mjs +42 -42
  128. package/dist/astro/routes/api/admin/transfer/exports/_id_/index.mjs +41 -41
  129. package/dist/astro/routes/api/admin/transfer/exports/_id_/manifest.mjs +42 -42
  130. package/dist/astro/routes/api/admin/transfer/exports/index.mjs +43 -43
  131. package/dist/astro/routes/api/admin/transfer/imports/_id_/abandon.mjs +41 -41
  132. package/dist/astro/routes/api/admin/transfer/imports/_id_/advance.mjs +41 -41
  133. package/dist/astro/routes/api/admin/transfer/imports/_id_/analyze.mjs +43 -43
  134. package/dist/astro/routes/api/admin/transfer/imports/_id_/cancel.mjs +41 -41
  135. package/dist/astro/routes/api/admin/transfer/imports/_id_/execute.mjs +43 -43
  136. package/dist/astro/routes/api/admin/transfer/imports/_id_/files/_...path_.mjs +41 -41
  137. package/dist/astro/routes/api/admin/transfer/imports/_id_/index.mjs +41 -41
  138. package/dist/astro/routes/api/admin/transfer/imports/_id_/missing.mjs +43 -43
  139. package/dist/astro/routes/api/admin/transfer/imports/_id_/plan.mjs +41 -41
  140. package/dist/astro/routes/api/admin/transfer/imports/_id_/receipt.mjs +41 -41
  141. package/dist/astro/routes/api/admin/transfer/imports/index.mjs +43 -43
  142. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +5 -5
  143. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +5 -5
  144. package/dist/astro/routes/api/admin/users/_id_/index.mjs +15 -15
  145. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +7 -7
  146. package/dist/astro/routes/api/admin/users/index.mjs +15 -15
  147. package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
  148. package/dist/astro/routes/api/auth/handover.d.mts +8 -0
  149. package/dist/astro/routes/api/auth/handover.d.mts.map +1 -0
  150. package/dist/astro/routes/api/auth/handover.mjs +49 -0
  151. package/dist/astro/routes/api/auth/handover.mjs.map +1 -0
  152. package/dist/astro/routes/api/auth/invite/accept.mjs +5 -5
  153. package/dist/astro/routes/api/auth/invite/complete.mjs +20 -20
  154. package/dist/astro/routes/api/auth/invite/index.mjs +17 -17
  155. package/dist/astro/routes/api/auth/invite/register-options.mjs +18 -18
  156. package/dist/astro/routes/api/auth/logout.mjs +6 -6
  157. package/dist/astro/routes/api/auth/magic-link/send.mjs +20 -19
  158. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  159. package/dist/astro/routes/api/auth/magic-link/verify.d.mts.map +1 -1
  160. package/dist/astro/routes/api/auth/magic-link/verify.mjs +24 -18
  161. package/dist/astro/routes/api/auth/magic-link/verify.mjs.map +1 -1
  162. package/dist/astro/routes/api/auth/me.mjs +16 -16
  163. package/dist/astro/routes/api/auth/mode.mjs +6 -6
  164. package/dist/astro/routes/api/auth/oauth/_provider_/callback.d.mts.map +1 -1
  165. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +36 -13
  166. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
  167. package/dist/astro/routes/api/auth/oauth/_provider_.d.mts.map +1 -1
  168. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +20 -5
  169. package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
  170. package/dist/astro/routes/api/auth/passkey/_id_.mjs +15 -15
  171. package/dist/astro/routes/api/auth/passkey/index.mjs +5 -5
  172. package/dist/astro/routes/api/auth/passkey/options.d.mts.map +1 -1
  173. package/dist/astro/routes/api/auth/passkey/options.mjs +28 -21
  174. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  175. package/dist/astro/routes/api/auth/passkey/register/options.mjs +18 -18
  176. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +19 -19
  177. package/dist/astro/routes/api/auth/passkey/verify.mjs +20 -20
  178. package/dist/astro/routes/api/auth/signup/complete.mjs +20 -20
  179. package/dist/astro/routes/api/auth/signup/request.mjs +19 -18
  180. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  181. package/dist/astro/routes/api/auth/signup/verify.mjs +5 -5
  182. package/dist/astro/routes/api/calendar.d.mts +8 -0
  183. package/dist/astro/routes/api/calendar.d.mts.map +1 -0
  184. package/dist/astro/routes/api/calendar.mjs +204 -0
  185. package/dist/astro/routes/api/calendar.mjs.map +1 -0
  186. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +14 -14
  187. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +20 -20
  188. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +6 -6
  189. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +17 -17
  190. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -6
  191. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +17 -17
  192. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +6 -6
  193. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +12 -12
  194. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +31 -31
  195. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +17 -17
  196. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +35 -35
  197. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +35 -35
  198. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +6 -6
  199. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +6 -6
  200. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +17 -17
  201. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +25 -25
  202. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +6 -6
  203. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +17 -17
  204. package/dist/astro/routes/api/content/_collection_/_id_.mjs +17 -17
  205. package/dist/astro/routes/api/content/_collection_/authors.mjs +6 -6
  206. package/dist/astro/routes/api/content/_collection_/index.mjs +16 -16
  207. package/dist/astro/routes/api/content/_collection_/trash.mjs +16 -16
  208. package/dist/astro/routes/api/dashboard.mjs +12 -12
  209. package/dist/astro/routes/api/dev/emails.mjs +6 -6
  210. package/dist/astro/routes/api/health.mjs +7 -7
  211. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  212. package/dist/astro/routes/api/import/probe.mjs +25 -25
  213. package/dist/astro/routes/api/import/wordpress/analyze.mjs +6 -6
  214. package/dist/astro/routes/api/import/wordpress/execute.d.mts +13 -13
  215. package/dist/astro/routes/api/import/wordpress/execute.mjs +31 -24
  216. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  217. package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -12
  218. package/dist/astro/routes/api/import/wordpress/media.mjs +20 -20
  219. package/dist/astro/routes/api/import/wordpress/prepare.mjs +18 -18
  220. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts +8 -0
  221. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.d.mts.map +1 -1
  222. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs +67 -31
  223. package/dist/astro/routes/api/import/wordpress/rewrite-url-helpers.mjs.map +1 -1
  224. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +13 -13
  225. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +27 -27
  226. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  227. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +25 -25
  228. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  229. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +13 -13
  230. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +40 -33
  231. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  232. package/dist/astro/routes/api/manifest.mjs +10 -7
  233. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  234. package/dist/astro/routes/api/mcp.mjs +94 -94
  235. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  236. package/dist/astro/routes/api/media/_id_/confirm.d.mts.map +1 -1
  237. package/dist/astro/routes/api/media/_id_/confirm.mjs +36 -20
  238. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  239. package/dist/astro/routes/api/media/_id_/replace.mjs +17 -17
  240. package/dist/astro/routes/api/media/_id_/upload.mjs +10 -10
  241. package/dist/astro/routes/api/media/_id_/usage.mjs +31 -31
  242. package/dist/astro/routes/api/media/_id_.mjs +29 -29
  243. package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +7 -7
  244. package/dist/astro/routes/api/media/file/_...key_.d.mts.map +1 -1
  245. package/dist/astro/routes/api/media/file/_...key_.mjs +46 -16
  246. package/dist/astro/routes/api/media/file/_...key_.mjs.map +1 -1
  247. package/dist/astro/routes/api/media/folders/_id_.mjs +17 -17
  248. package/dist/astro/routes/api/media/folders/index.mjs +17 -17
  249. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +6 -6
  250. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +6 -6
  251. package/dist/astro/routes/api/media/providers/index.mjs +6 -6
  252. package/dist/astro/routes/api/media/upload-url.mjs +45 -25
  253. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  254. package/dist/astro/routes/api/media.d.mts.map +1 -1
  255. package/dist/astro/routes/api/media.mjs +47 -36
  256. package/dist/astro/routes/api/media.mjs.map +1 -1
  257. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +18 -18
  258. package/dist/astro/routes/api/menus/_name_/items.mjs +18 -18
  259. package/dist/astro/routes/api/menus/_name_/reorder.mjs +18 -18
  260. package/dist/astro/routes/api/menus/_name_/translations.mjs +18 -18
  261. package/dist/astro/routes/api/menus/_name_.mjs +18 -18
  262. package/dist/astro/routes/api/menus/index.mjs +18 -18
  263. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  264. package/dist/astro/routes/api/oauth/device/authorize.mjs +9 -9
  265. package/dist/astro/routes/api/oauth/device/code.mjs +13 -12
  266. package/dist/astro/routes/api/oauth/device/code.mjs.map +1 -1
  267. package/dist/astro/routes/api/oauth/device/token.mjs +12 -11
  268. package/dist/astro/routes/api/oauth/device/token.mjs.map +1 -1
  269. package/dist/astro/routes/api/oauth/register.mjs +9 -8
  270. package/dist/astro/routes/api/oauth/register.mjs.map +1 -1
  271. package/dist/astro/routes/api/oauth/token/refresh.mjs +9 -9
  272. package/dist/astro/routes/api/oauth/token/revoke.mjs +9 -9
  273. package/dist/astro/routes/api/oauth/token.mjs +9 -9
  274. package/dist/astro/routes/api/openapi.json.mjs +23 -23
  275. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  276. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +12 -12
  277. package/dist/astro/routes/api/redirects/404s/index.mjs +20 -19
  278. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  279. package/dist/astro/routes/api/redirects/404s/summary.mjs +20 -19
  280. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  281. package/dist/astro/routes/api/redirects/_id_.mjs +20 -19
  282. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  283. package/dist/astro/routes/api/redirects/index.mjs +20 -19
  284. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  285. package/dist/astro/routes/api/relations/_id_/index.mjs +35 -35
  286. package/dist/astro/routes/api/relations/index.mjs +35 -35
  287. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +6 -6
  288. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +17 -17
  289. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs +71 -70
  290. package/dist/astro/routes/api/schema/block-types/_slug_/index.mjs.map +1 -1
  291. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs +71 -70
  292. package/dist/astro/routes/api/schema/block-types/_slug_/versions/_version_/activate.mjs.map +1 -1
  293. package/dist/astro/routes/api/schema/block-types/index.mjs +71 -70
  294. package/dist/astro/routes/api/schema/block-types/index.mjs.map +1 -1
  295. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +71 -70
  296. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  297. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +71 -70
  298. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  299. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +71 -70
  300. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  301. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +71 -70
  302. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  303. package/dist/astro/routes/api/schema/collections/index.mjs +71 -70
  304. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  305. package/dist/astro/routes/api/schema/collections/reorder.mjs +71 -70
  306. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  307. package/dist/astro/routes/api/schema/index.mjs +17 -17
  308. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  309. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +71 -70
  310. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  311. package/dist/astro/routes/api/schema/orphans/index.mjs +71 -70
  312. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  313. package/dist/astro/routes/api/search/enable.mjs +19 -19
  314. package/dist/astro/routes/api/search/index.mjs +18 -18
  315. package/dist/astro/routes/api/search/rebuild.mjs +19 -19
  316. package/dist/astro/routes/api/search/stats.mjs +9 -9
  317. package/dist/astro/routes/api/search/suggest.mjs +18 -18
  318. package/dist/astro/routes/api/sections/_slug_.mjs +18 -18
  319. package/dist/astro/routes/api/sections/index.mjs +18 -18
  320. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +8 -8
  321. package/dist/astro/routes/api/settings/backups/archives/index.mjs +8 -8
  322. package/dist/astro/routes/api/settings/backups/export.mjs +8 -8
  323. package/dist/astro/routes/api/settings/backups/index.mjs +9 -9
  324. package/dist/astro/routes/api/settings/domain-notify.d.mts +8 -0
  325. package/dist/astro/routes/api/settings/domain-notify.d.mts.map +1 -0
  326. package/dist/astro/routes/api/settings/domain-notify.mjs +125 -0
  327. package/dist/astro/routes/api/settings/domain-notify.mjs.map +1 -0
  328. package/dist/astro/routes/api/settings/domain.d.mts +9 -0
  329. package/dist/astro/routes/api/settings/domain.d.mts.map +1 -0
  330. package/dist/astro/routes/api/settings/domain.mjs +61 -0
  331. package/dist/astro/routes/api/settings/domain.mjs.map +1 -0
  332. package/dist/astro/routes/api/settings/email.mjs +7 -7
  333. package/dist/astro/routes/api/settings.mjs +22 -22
  334. package/dist/astro/routes/api/setup/admin-verify.mjs +21 -21
  335. package/dist/astro/routes/api/setup/admin.mjs +19 -19
  336. package/dist/astro/routes/api/setup/dev-bypass.mjs +43 -43
  337. package/dist/astro/routes/api/setup/dev-reset.mjs +5 -5
  338. package/dist/astro/routes/api/setup/index.d.mts.map +1 -1
  339. package/dist/astro/routes/api/setup/index.mjs +59 -50
  340. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  341. package/dist/astro/routes/api/setup/status.mjs +7 -7
  342. package/dist/astro/routes/api/site/domain-proof.d.mts +8 -0
  343. package/dist/astro/routes/api/site/domain-proof.d.mts.map +1 -0
  344. package/dist/astro/routes/api/site/domain-proof.mjs +33 -0
  345. package/dist/astro/routes/api/site/domain-proof.mjs.map +1 -0
  346. package/dist/astro/routes/api/snapshot.mjs +10 -10
  347. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +23 -23
  348. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +23 -23
  349. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +23 -23
  350. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +23 -23
  351. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +22 -22
  352. package/dist/astro/routes/api/taxonomies/_name_.mjs +23 -23
  353. package/dist/astro/routes/api/taxonomies/bulk-tag.mjs +36 -36
  354. package/dist/astro/routes/api/taxonomies/index.mjs +23 -23
  355. package/dist/astro/routes/api/themes/preview.mjs +8 -8
  356. package/dist/astro/routes/api/typegen.mjs +7 -7
  357. package/dist/astro/routes/api/visual-editing/action-token.mjs +8 -8
  358. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +20 -20
  359. package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +5 -5
  360. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  361. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  362. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  363. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +17 -17
  364. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +19 -19
  365. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +19 -19
  366. package/dist/astro/routes/api/widget-areas/_name_.mjs +9 -9
  367. package/dist/astro/routes/api/widget-areas/index.mjs +19 -19
  368. package/dist/astro/routes/api/widget-components.mjs +6 -6
  369. package/dist/astro/routes/robots.txt.mjs +7 -7
  370. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  371. package/dist/astro/routes/sitemap-_collection_.xml.mjs +30 -13
  372. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  373. package/dist/astro/routes/sitemap.xml.d.mts.map +1 -1
  374. package/dist/astro/routes/sitemap.xml.mjs +14 -13
  375. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  376. package/dist/astro/types.d.mts +30 -18
  377. package/dist/astro/types.d.mts.map +1 -1
  378. package/dist/audit-CCH3MA4Y.mjs +127 -0
  379. package/dist/audit-CCH3MA4Y.mjs.map +1 -0
  380. package/dist/auth/providers/github.d.mts +1 -1
  381. package/dist/auth/providers/google.d.mts +1 -1
  382. package/dist/auth/providers/microsoft.d.mts +27 -0
  383. package/dist/auth/providers/microsoft.d.mts.map +1 -0
  384. package/dist/auth/providers/microsoft.mjs +20 -0
  385. package/dist/auth/providers/microsoft.mjs.map +1 -0
  386. package/dist/authoritative-records-Dcs7l2H5.mjs +5183 -0
  387. package/dist/authoritative-records-Dcs7l2H5.mjs.map +1 -0
  388. package/dist/authorize-TdIGpRSE.mjs +49 -0
  389. package/dist/authorize-TdIGpRSE.mjs.map +1 -0
  390. package/dist/backup-CZt5Mzwx.mjs +246 -0
  391. package/dist/backup-CZt5Mzwx.mjs.map +1 -0
  392. package/dist/base64-BQBZwXCx.mjs +63 -0
  393. package/dist/base64-BQBZwXCx.mjs.map +1 -0
  394. package/dist/block-types-DHnkzE6u.mjs +132 -0
  395. package/dist/block-types-DHnkzE6u.mjs.map +1 -0
  396. package/dist/block-values-C4N_F2-G.mjs +175 -0
  397. package/dist/block-values-C4N_F2-G.mjs.map +1 -0
  398. package/dist/byline-BmVKOQ8l.mjs +909 -0
  399. package/dist/byline-BmVKOQ8l.mjs.map +1 -0
  400. package/dist/byline-fields-B8SDs3M_.mjs +123 -0
  401. package/dist/byline-fields-B8SDs3M_.mjs.map +1 -0
  402. package/dist/byline-fields-Bze3ZdAw.mjs +280 -0
  403. package/dist/byline-fields-Bze3ZdAw.mjs.map +1 -0
  404. package/dist/bylines-CHhR28r9.mjs +150 -0
  405. package/dist/bylines-CHhR28r9.mjs.map +1 -0
  406. package/dist/bylines-DZS4OflA.mjs +234 -0
  407. package/dist/bylines-DZS4OflA.mjs.map +1 -0
  408. package/dist/cache-x0z8t_m9.mjs +133 -0
  409. package/dist/cache-x0z8t_m9.mjs.map +1 -0
  410. package/dist/canonical-BUcKRYpG.mjs +174 -0
  411. package/dist/canonical-BUcKRYpG.mjs.map +1 -0
  412. package/dist/capture-triggers-C9JKoIdT.mjs +362 -0
  413. package/dist/capture-triggers-C9JKoIdT.mjs.map +1 -0
  414. package/dist/challenge-store-BHgdPVze.mjs +51 -0
  415. package/dist/challenge-store-BHgdPVze.mjs.map +1 -0
  416. package/dist/checksum-BYR5i7qe.mjs +162 -0
  417. package/dist/checksum-BYR5i7qe.mjs.map +1 -0
  418. package/dist/chrome-tags-BIKBMZYO.mjs +24 -0
  419. package/dist/chrome-tags-BIKBMZYO.mjs.map +1 -0
  420. package/dist/chunks-Dr5Vr0zA.mjs +25 -0
  421. package/dist/chunks-Dr5Vr0zA.mjs.map +1 -0
  422. package/dist/cli/index.mjs +54 -52
  423. package/dist/cli/index.mjs.map +1 -1
  424. package/dist/client/cf-access.d.mts +1 -1
  425. package/dist/client/index.d.mts +34 -34
  426. package/dist/client/index.d.mts.map +1 -1
  427. package/dist/client/index.mjs +2 -2
  428. package/dist/client/index.mjs.map +1 -1
  429. package/dist/collection-slugs-cache-B2Gs9Jwx.mjs +85 -0
  430. package/dist/collection-slugs-cache-B2Gs9Jwx.mjs.map +1 -0
  431. package/dist/comment-CWla6OHV.mjs +325 -0
  432. package/dist/comment-CWla6OHV.mjs.map +1 -0
  433. package/dist/comment-reaction-RZrcs5cW.mjs +85 -0
  434. package/dist/comment-reaction-RZrcs5cW.mjs.map +1 -0
  435. package/dist/comments-BzmIKVb2.mjs +99 -0
  436. package/dist/comments-BzmIKVb2.mjs.map +1 -0
  437. package/dist/comments-Da_dI2O1.mjs +195 -0
  438. package/dist/comments-Da_dI2O1.mjs.map +1 -0
  439. package/dist/components-oFXumpf5.mjs +112 -0
  440. package/dist/components-oFXumpf5.mjs.map +1 -0
  441. package/dist/config--wamYxBW.d.mts +39 -0
  442. package/dist/config--wamYxBW.d.mts.map +1 -0
  443. package/dist/config-Bo4eOOdV.mjs +228 -0
  444. package/dist/config-Bo4eOOdV.mjs.map +1 -0
  445. package/dist/config-C4-UxIk7.d.mts +6790 -0
  446. package/dist/config-C4-UxIk7.d.mts.map +1 -0
  447. package/dist/connection-UpzYxtE2.mjs +36 -0
  448. package/dist/connection-UpzYxtE2.mjs.map +1 -0
  449. package/dist/content-CcHrqkgp.mjs +2072 -0
  450. package/dist/content-CcHrqkgp.mjs.map +1 -0
  451. package/dist/content-list-query-Bxxdd9fv.d.mts +26 -0
  452. package/dist/content-list-query-Bxxdd9fv.d.mts.map +1 -0
  453. package/dist/content-repair-havk1F0-.mjs +442 -0
  454. package/dist/content-repair-havk1F0-.mjs.map +1 -0
  455. package/dist/context-CvhgHgSH.mjs +2043 -0
  456. package/dist/context-CvhgHgSH.mjs.map +1 -0
  457. package/dist/credits-CygUrPOi.mjs +110 -0
  458. package/dist/credits-CygUrPOi.mjs.map +1 -0
  459. package/dist/cron-C6ytTOLw.mjs +306 -0
  460. package/dist/cron-C6ytTOLw.mjs.map +1 -0
  461. package/dist/dashboard-7_FYfafc.mjs +151 -0
  462. package/dist/dashboard-7_FYfafc.mjs.map +1 -0
  463. package/dist/db/index.d.mts +3 -3
  464. package/dist/db/index.mjs +1 -1
  465. package/dist/db/index.mjs.map +1 -1
  466. package/dist/db/libsql-migrations.d.mts +1 -1
  467. package/dist/db/libsql-migrations.mjs +5 -5
  468. package/dist/db/libsql.d.mts +1 -1
  469. package/dist/db/postgres-migrations.d.mts +1 -1
  470. package/dist/db/postgres-migrations.mjs +4 -4
  471. package/dist/db/postgres.d.mts +1 -1
  472. package/dist/db/sqlite-migrations.d.mts +1 -1
  473. package/dist/db/sqlite-migrations.mjs +4 -4
  474. package/dist/db/sqlite.d.mts +1 -1
  475. package/dist/db/sqlite.mjs +1 -1
  476. package/dist/default-CHCUkV4Z.mjs +81 -0
  477. package/dist/default-CHCUkV4Z.mjs.map +1 -0
  478. package/dist/deferred-tasks-G0xhcJTl.d.mts +9 -0
  479. package/dist/deferred-tasks-G0xhcJTl.d.mts.map +1 -0
  480. package/dist/dev-typegen-Dtl8AfS6.mjs +29 -0
  481. package/dist/dev-typegen-Dtl8AfS6.mjs.map +1 -0
  482. package/dist/device-flow-CzDJmQ1q.mjs +521 -0
  483. package/dist/device-flow-CzDJmQ1q.mjs.map +1 -0
  484. package/dist/digest-xJmyPv2r.mjs +194 -0
  485. package/dist/digest-xJmyPv2r.mjs.map +1 -0
  486. package/dist/direct-executor-BXiYIVJ9.mjs +104 -0
  487. package/dist/direct-executor-BXiYIVJ9.mjs.map +1 -0
  488. package/dist/email-console-7ZPApLlm.mjs +50 -0
  489. package/dist/email-console-7ZPApLlm.mjs.map +1 -0
  490. package/dist/email-locale-DFdUA6UF.mjs +25 -0
  491. package/dist/email-locale-DFdUA6UF.mjs.map +1 -0
  492. package/dist/emdash-runtime-C4oFeVpN.mjs +4363 -0
  493. package/dist/emdash-runtime-C4oFeVpN.mjs.map +1 -0
  494. package/dist/enrich-Duu4sV3a.mjs +194 -0
  495. package/dist/enrich-Duu4sV3a.mjs.map +1 -0
  496. package/dist/entry-lock-B4n0HdQ_.mjs +163 -0
  497. package/dist/entry-lock-B4n0HdQ_.mjs.map +1 -0
  498. package/dist/entry-lock-BAAlKYrz.d.mts +4566 -0
  499. package/dist/entry-lock-BAAlKYrz.d.mts.map +1 -0
  500. package/dist/entry-lock-Cjz0h45X.mjs +1209 -0
  501. package/dist/entry-lock-Cjz0h45X.mjs.map +1 -0
  502. package/dist/env-types-4bkF6Bqg.mjs +38 -0
  503. package/dist/env-types-4bkF6Bqg.mjs.map +1 -0
  504. package/dist/error-DxgQdaZ_.mjs +90 -0
  505. package/dist/error-DxgQdaZ_.mjs.map +1 -0
  506. package/dist/errors-CjtE5aWF.mjs +120 -0
  507. package/dist/errors-CjtE5aWF.mjs.map +1 -0
  508. package/dist/errors-hskQ0C8G.mjs +479 -0
  509. package/dist/errors-hskQ0C8G.mjs.map +1 -0
  510. package/dist/escape-ESFhYzwG.mjs +9 -0
  511. package/dist/escape-ESFhYzwG.mjs.map +1 -0
  512. package/dist/fence-vc3aHgRA.mjs +162 -0
  513. package/dist/fence-vc3aHgRA.mjs.map +1 -0
  514. package/dist/field-defs-cache-zoow36nz.mjs +77 -0
  515. package/dist/field-defs-cache-zoow36nz.mjs.map +1 -0
  516. package/dist/field-map-JD1wG0Tq.mjs +83 -0
  517. package/dist/field-map-JD1wG0Tq.mjs.map +1 -0
  518. package/dist/fts-manager-WJCN8uU6.mjs +443 -0
  519. package/dist/fts-manager-WJCN8uU6.mjs.map +1 -0
  520. package/dist/gc-CsGZiKdH.mjs +75 -0
  521. package/dist/gc-CsGZiKdH.mjs.map +1 -0
  522. package/dist/hash-C4DFoJ7Z.mjs +37 -0
  523. package/dist/hash-C4DFoJ7Z.mjs.map +1 -0
  524. package/dist/hook-result-CXjtMk7Q.d.mts +31 -0
  525. package/dist/hook-result-CXjtMk7Q.d.mts.map +1 -0
  526. package/dist/hreflang-BAma7iRr.mjs +105 -0
  527. package/dist/hreflang-BAma7iRr.mjs.map +1 -0
  528. package/dist/hreflang-BsYo4x_M.d.mts +50 -0
  529. package/dist/hreflang-BsYo4x_M.d.mts.map +1 -0
  530. package/dist/http-route-dispatch-C8G7hAK6.mjs +342 -0
  531. package/dist/http-route-dispatch-C8G7hAK6.mjs.map +1 -0
  532. package/dist/identity-BwBKM1fH.mjs +35 -0
  533. package/dist/identity-BwBKM1fH.mjs.map +1 -0
  534. package/dist/import-DJ3Z2wxE.mjs +2021 -0
  535. package/dist/import-DJ3Z2wxE.mjs.map +1 -0
  536. package/dist/index-DYsn97dY.d.mts +623 -0
  537. package/dist/index-DYsn97dY.d.mts.map +1 -0
  538. package/dist/index.d.mts +24 -24
  539. package/dist/index.mjs +99 -98
  540. package/dist/init-lock-BGmew3r1.mjs +56 -0
  541. package/dist/init-lock-BGmew3r1.mjs.map +1 -0
  542. package/dist/install-finalization-DCFnFSHv.mjs +58 -0
  543. package/dist/install-finalization-DCFnFSHv.mjs.map +1 -0
  544. package/dist/keys-CGvAndSp.mjs +61 -0
  545. package/dist/keys-CGvAndSp.mjs.map +1 -0
  546. package/dist/kinds-DT1W4T4Y.mjs +974 -0
  547. package/dist/kinds-DT1W4T4Y.mjs.map +1 -0
  548. package/dist/lifecycle-BzglCSY0.mjs +25 -0
  549. package/dist/lifecycle-BzglCSY0.mjs.map +1 -0
  550. package/dist/load-DjUqmf3s.mjs +28 -0
  551. package/dist/load-DjUqmf3s.mjs.map +1 -0
  552. package/dist/loader-CetY659y.mjs +1355 -0
  553. package/dist/loader-CetY659y.mjs.map +1 -0
  554. package/dist/loops-DkT1zkPH.mjs +205 -0
  555. package/dist/loops-DkT1zkPH.mjs.map +1 -0
  556. package/dist/manifest-builder-BOFCd9rX.mjs +189 -0
  557. package/dist/manifest-builder-BOFCd9rX.mjs.map +1 -0
  558. package/dist/manifest-schema-BiFBPEsp.mjs +497 -0
  559. package/dist/manifest-schema-BiFBPEsp.mjs.map +1 -0
  560. package/dist/marketplace-CUlenvZm.mjs +257 -0
  561. package/dist/marketplace-CUlenvZm.mjs.map +1 -0
  562. package/dist/media/image-endpoint.d.mts +26 -2
  563. package/dist/media/image-endpoint.d.mts.map +1 -1
  564. package/dist/media/image-endpoint.mjs +73 -3
  565. package/dist/media/image-endpoint.mjs.map +1 -1
  566. package/dist/media/index.d.mts +1 -1
  567. package/dist/media/index.mjs +1 -1
  568. package/dist/media/local-runtime.d.mts +14 -14
  569. package/dist/media/local-runtime.mjs +7 -7
  570. package/dist/media-CRKsKlfK.mjs +416 -0
  571. package/dist/media-CRKsKlfK.mjs.map +1 -0
  572. package/dist/media-C_z7sRuC.mjs +294 -0
  573. package/dist/media-C_z7sRuC.mjs.map +1 -0
  574. package/dist/media-IT8hQleU.mjs +373 -0
  575. package/dist/media-IT8hQleU.mjs.map +1 -0
  576. package/dist/media-allowlist-Ct6wvnT2.mjs +45 -0
  577. package/dist/media-allowlist-Ct6wvnT2.mjs.map +1 -0
  578. package/dist/media-folders-DtMt-vAA.mjs +210 -0
  579. package/dist/media-folders-DtMt-vAA.mjs.map +1 -0
  580. package/dist/media-url-BpXLqCmf.mjs +43 -0
  581. package/dist/media-url-BpXLqCmf.mjs.map +1 -0
  582. package/dist/media-usage-DEPHZZ9X.mjs +282 -0
  583. package/dist/media-usage-DEPHZZ9X.mjs.map +1 -0
  584. package/dist/media-usage-activation-V2Siabtw.mjs +78 -0
  585. package/dist/media-usage-activation-V2Siabtw.mjs.map +1 -0
  586. package/dist/media-usage-work-Cv1OE59F.mjs +151 -0
  587. package/dist/media-usage-work-Cv1OE59F.mjs.map +1 -0
  588. package/dist/media-usage-work-cgDYGvrt.mjs +1015 -0
  589. package/dist/media-usage-work-cgDYGvrt.mjs.map +1 -0
  590. package/dist/menus-CvVYfsuk.mjs +753 -0
  591. package/dist/menus-CvVYfsuk.mjs.map +1 -0
  592. package/dist/menus-D1HC8npj.mjs +92 -0
  593. package/dist/menus-D1HC8npj.mjs.map +1 -0
  594. package/dist/menus-DL8IPIcm.mjs +3721 -0
  595. package/dist/menus-DL8IPIcm.mjs.map +1 -0
  596. package/dist/migrations/index.d.mts +1 -1
  597. package/dist/migrations/index.mjs +3 -3
  598. package/dist/mime-B5M9R53H.mjs +81 -0
  599. package/dist/mime-B5M9R53H.mjs.map +1 -0
  600. package/dist/mode-eieqAWLe.mjs +23 -0
  601. package/dist/mode-eieqAWLe.mjs.map +1 -0
  602. package/dist/node-sqlite-compat-VuyzJw1b.mjs +63 -0
  603. package/dist/node-sqlite-compat-VuyzJw1b.mjs.map +1 -0
  604. package/dist/oauth-authorization-B6ZJbMd2.mjs +274 -0
  605. package/dist/oauth-authorization-B6ZJbMd2.mjs.map +1 -0
  606. package/dist/oauth-clients-DVjRc9v3.mjs +266 -0
  607. package/dist/oauth-clients-DVjRc9v3.mjs.map +1 -0
  608. package/dist/oauth-state-store-CnmYYsVQ.mjs +50 -0
  609. package/dist/oauth-state-store-CnmYYsVQ.mjs.map +1 -0
  610. package/dist/oauth-user-lookup-Dz1_R5vT.mjs +26 -0
  611. package/dist/oauth-user-lookup-Dz1_R5vT.mjs.map +1 -0
  612. package/dist/object-cache/memory.d.mts +1 -1
  613. package/dist/options-BR9XZl7h.d.mts +81 -0
  614. package/dist/options-BR9XZl7h.d.mts.map +1 -0
  615. package/dist/ownership-COPyVysP.mjs +12 -0
  616. package/dist/ownership-COPyVysP.mjs.map +1 -0
  617. package/dist/page/index.d.mts +2 -2
  618. package/dist/page/index.mjs +6 -6
  619. package/dist/parse-BOGhrwSq.d.mts +174 -0
  620. package/dist/parse-BOGhrwSq.d.mts.map +1 -0
  621. package/dist/parse-Cn5aSk1N.mjs +81 -0
  622. package/dist/parse-Cn5aSk1N.mjs.map +1 -0
  623. package/dist/passkey-config-mMFRQUCj.mjs +46 -0
  624. package/dist/passkey-config-mMFRQUCj.mjs.map +1 -0
  625. package/dist/paths-BmcAdBo9.mjs +77 -0
  626. package/dist/paths-BmcAdBo9.mjs.map +1 -0
  627. package/dist/patterns-CMNnYCgf.mjs +159 -0
  628. package/dist/patterns-CMNnYCgf.mjs.map +1 -0
  629. package/dist/placeholder-nOep3_Gn.d.mts +306 -0
  630. package/dist/placeholder-nOep3_Gn.d.mts.map +1 -0
  631. package/dist/plan-CDY4K8CH.mjs +135 -0
  632. package/dist/plan-CDY4K8CH.mjs.map +1 -0
  633. package/dist/plugin-test-runtime.d.mts +14 -14
  634. package/dist/plugin-test-runtime.d.mts.map +1 -1
  635. package/dist/plugin-test-runtime.mjs +111 -110
  636. package/dist/plugin-types.d.mts +2 -2
  637. package/dist/plugin-utils.d.mts +12 -12
  638. package/dist/plugin-utils.mjs +1 -1
  639. package/dist/plugin-utils.mjs.map +1 -1
  640. package/dist/plugins/adapt-sandbox-entry.d.mts +12 -12
  641. package/dist/plugins/adapt-sandbox-entry.mjs +3 -3
  642. package/dist/plugins/host.d.mts +5 -5
  643. package/dist/plugins/host.mjs +45 -44
  644. package/dist/plugins-DFBpfbO3.mjs +261 -0
  645. package/dist/plugins-DFBpfbO3.mjs.map +1 -0
  646. package/dist/policy-B_2qnHSy.mjs +45 -0
  647. package/dist/policy-B_2qnHSy.mjs.map +1 -0
  648. package/dist/portable-text-Dc2YAusw.mjs +307 -0
  649. package/dist/portable-text-Dc2YAusw.mjs.map +1 -0
  650. package/dist/preview-BtzikJ9O.mjs +107 -0
  651. package/dist/preview-BtzikJ9O.mjs.map +1 -0
  652. package/dist/protocol-M268ox8L.d.mts +61 -0
  653. package/dist/protocol-M268ox8L.d.mts.map +1 -0
  654. package/dist/public-submission-BQZavgZU.mjs +251 -0
  655. package/dist/public-submission-BQZavgZU.mjs.map +1 -0
  656. package/dist/public-url-uT1Ok2Tu.mjs +101 -0
  657. package/dist/public-url-uT1Ok2Tu.mjs.map +1 -0
  658. package/dist/query-BGG3sDEd.mjs +1127 -0
  659. package/dist/query-BGG3sDEd.mjs.map +1 -0
  660. package/dist/query-C_CWlifp.mjs +41 -0
  661. package/dist/query-C_CWlifp.mjs.map +1 -0
  662. package/dist/range-CXK_kDbI.mjs +51 -0
  663. package/dist/range-CXK_kDbI.mjs.map +1 -0
  664. package/dist/rate-limit-Dvamvay-.mjs +127 -0
  665. package/dist/rate-limit-Dvamvay-.mjs.map +1 -0
  666. package/dist/receipt-BmQGzquP.mjs +43 -0
  667. package/dist/receipt-BmQGzquP.mjs.map +1 -0
  668. package/dist/redirect-B39kTQ_0.mjs +423 -0
  669. package/dist/redirect-B39kTQ_0.mjs.map +1 -0
  670. package/dist/redirect-BguJ00Pv.mjs +19 -0
  671. package/dist/redirect-BguJ00Pv.mjs.map +1 -0
  672. package/dist/redirects-C8G4BsfP.mjs +446 -0
  673. package/dist/redirects-C8G4BsfP.mjs.map +1 -0
  674. package/dist/redirects-DFT7Wr-e.mjs +148 -0
  675. package/dist/redirects-DFT7Wr-e.mjs.map +1 -0
  676. package/dist/registry/testing.mjs +3 -3
  677. package/dist/registry-DeZ27gVi.mjs +8374 -0
  678. package/dist/registry-DeZ27gVi.mjs.map +1 -0
  679. package/dist/relations-DBB1bRJW.mjs +776 -0
  680. package/dist/relations-DBB1bRJW.mjs.map +1 -0
  681. package/dist/request-context.d.mts +2 -2
  682. package/dist/request-context.mjs.map +1 -1
  683. package/dist/request-meta-BSvkhIBX.mjs +141 -0
  684. package/dist/request-meta-BSvkhIBX.mjs.map +1 -0
  685. package/dist/resolve-BxH64HN9.mjs +100 -0
  686. package/dist/resolve-BxH64HN9.mjs.map +1 -0
  687. package/dist/revision-BWEH2qQB.mjs +368 -0
  688. package/dist/revision-BWEH2qQB.mjs.map +1 -0
  689. package/dist/route-wire-CwbWB_6O.mjs +197 -0
  690. package/dist/route-wire-CwbWB_6O.mjs.map +1 -0
  691. package/dist/routes-BfIxc7ft.mjs +272 -0
  692. package/dist/routes-BfIxc7ft.mjs.map +1 -0
  693. package/dist/runner-DkeafPBA.d.mts +68 -0
  694. package/dist/runner-DkeafPBA.d.mts.map +1 -0
  695. package/dist/runner-wqt4BrIW.mjs +6383 -0
  696. package/dist/runner-wqt4BrIW.mjs.map +1 -0
  697. package/dist/runtime.d.mts +13 -13
  698. package/dist/runtime.mjs +3 -3
  699. package/dist/schema/project-env-types.mjs +38 -38
  700. package/dist/schema-BXIW-JHi.mjs +815 -0
  701. package/dist/schema-BXIW-JHi.mjs.map +1 -0
  702. package/dist/schema-I74JwYte.mjs +13 -0
  703. package/dist/schema-I74JwYte.mjs.map +1 -0
  704. package/dist/scopes-RWZ5ft_Z.mjs +34 -0
  705. package/dist/scopes-RWZ5ft_Z.mjs.map +1 -0
  706. package/dist/search-DxwagyWS.mjs +450 -0
  707. package/dist/search-DxwagyWS.mjs.map +1 -0
  708. package/dist/secrets-Cc_O3Kp-.mjs +369 -0
  709. package/dist/secrets-Cc_O3Kp-.mjs.map +1 -0
  710. package/dist/sections-Ccq9ZgyD.mjs +346 -0
  711. package/dist/sections-Ccq9ZgyD.mjs.map +1 -0
  712. package/dist/seed/index.d.mts +2 -2
  713. package/dist/seed/index.mjs +36 -36
  714. package/dist/seo/index.d.mts +3 -3
  715. package/dist/seo/index.mjs +23 -23
  716. package/dist/seo-CCnfPWAQ.mjs +133 -0
  717. package/dist/seo-CCnfPWAQ.mjs.map +1 -0
  718. package/dist/seo-QCtJ1jjV.mjs +188 -0
  719. package/dist/seo-QCtJ1jjV.mjs.map +1 -0
  720. package/dist/service-DX84s2rw.mjs +150 -0
  721. package/dist/service-DX84s2rw.mjs.map +1 -0
  722. package/dist/session-user-1dtJc2zv.mjs +61 -0
  723. package/dist/session-user-1dtJc2zv.mjs.map +1 -0
  724. package/dist/settings-B1jOxAIl.mjs +443 -0
  725. package/dist/settings-B1jOxAIl.mjs.map +1 -0
  726. package/dist/settings-Bc1IBl1t.mjs +66 -0
  727. package/dist/settings-Bc1IBl1t.mjs.map +1 -0
  728. package/dist/settings-GaZ5sVgg.mjs +73 -0
  729. package/dist/settings-GaZ5sVgg.mjs.map +1 -0
  730. package/dist/setup-complete-DidMAWJS.mjs +65 -0
  731. package/dist/setup-complete-DidMAWJS.mjs.map +1 -0
  732. package/dist/setup-nonce-DKl4S4c6.mjs +25 -0
  733. package/dist/setup-nonce-DKl4S4c6.mjs.map +1 -0
  734. package/dist/single-flight-cache-D0EaGbN1.mjs +104 -0
  735. package/dist/single-flight-cache-D0EaGbN1.mjs.map +1 -0
  736. package/dist/site-domain-CbI51Pc2.mjs +113 -0
  737. package/dist/site-domain-CbI51Pc2.mjs.map +1 -0
  738. package/dist/site-settings-CDdT4mVm.mjs +50 -0
  739. package/dist/site-settings-CDdT4mVm.mjs.map +1 -0
  740. package/dist/site-url-CrDZaAvo.mjs +45 -0
  741. package/dist/site-url-CrDZaAvo.mjs.map +1 -0
  742. package/dist/sitemap-name-W3BTor6X.mjs +26 -0
  743. package/dist/sitemap-name-W3BTor6X.mjs.map +1 -0
  744. package/dist/slug-BJhHKoOH.mjs +16 -0
  745. package/dist/slug-BJhHKoOH.mjs.map +1 -0
  746. package/dist/slugify-bu2-jKia.mjs +22 -0
  747. package/dist/slugify-bu2-jKia.mjs.map +1 -0
  748. package/dist/snapshot-BuI_2uDe.mjs +414 -0
  749. package/dist/snapshot-BuI_2uDe.mjs.map +1 -0
  750. package/dist/ssrf-LM_0wpGA.mjs +406 -0
  751. package/dist/ssrf-LM_0wpGA.mjs.map +1 -0
  752. package/dist/stage-DhzZuYaT.mjs +662 -0
  753. package/dist/stage-DhzZuYaT.mjs.map +1 -0
  754. package/dist/staged-DboEaWsV.mjs +686 -0
  755. package/dist/staged-DboEaWsV.mjs.map +1 -0
  756. package/dist/state-DCXDzjHq.mjs +178 -0
  757. package/dist/state-DCXDzjHq.mjs.map +1 -0
  758. package/dist/status-Dl85BK39.mjs +30 -0
  759. package/dist/status-Dl85BK39.mjs.map +1 -0
  760. package/dist/storage/local.d.mts +2 -2
  761. package/dist/storage/local.d.mts.map +1 -1
  762. package/dist/storage/local.mjs +12 -5
  763. package/dist/storage/local.mjs.map +1 -1
  764. package/dist/storage/s3.d.mts +4 -2
  765. package/dist/storage/s3.d.mts.map +1 -1
  766. package/dist/storage/s3.mjs +56 -9
  767. package/dist/storage/s3.mjs.map +1 -1
  768. package/dist/storage-query-BOBmeDMD.d.mts +1615 -0
  769. package/dist/storage-query-BOBmeDMD.d.mts.map +1 -0
  770. package/dist/tar-K40edFMt.mjs +449 -0
  771. package/dist/tar-K40edFMt.mjs.map +1 -0
  772. package/dist/target-BOighEY1.mjs +27 -0
  773. package/dist/target-BOighEY1.mjs.map +1 -0
  774. package/dist/taxonomies-DQSywBvX.mjs +697 -0
  775. package/dist/taxonomies-DQSywBvX.mjs.map +1 -0
  776. package/dist/taxonomies-Dj5eaobC.mjs +939 -0
  777. package/dist/taxonomies-Dj5eaobC.mjs.map +1 -0
  778. package/dist/taxonomy-QrNmTfIZ.mjs +550 -0
  779. package/dist/taxonomy-QrNmTfIZ.mjs.map +1 -0
  780. package/dist/tokens-j5L0as2H.mjs +171 -0
  781. package/dist/tokens-j5L0as2H.mjs.map +1 -0
  782. package/dist/transfer-CYEw8Tu4.mjs +8873 -0
  783. package/dist/transfer-CYEw8Tu4.mjs.map +1 -0
  784. package/dist/transfer-DDX10hkD.mjs +166 -0
  785. package/dist/transfer-DDX10hkD.mjs.map +1 -0
  786. package/dist/transfer-download-BAqq6GJv.mjs +33 -0
  787. package/dist/transfer-download-BAqq6GJv.mjs.map +1 -0
  788. package/dist/transformations-De44wqV_.mjs +196 -0
  789. package/dist/transformations-De44wqV_.mjs.map +1 -0
  790. package/dist/transport-1QjUhCRO.d.mts +42 -0
  791. package/dist/transport-1QjUhCRO.d.mts.map +1 -0
  792. package/dist/transport-Ds5pVX7p.mjs +136 -0
  793. package/dist/transport-Ds5pVX7p.mjs.map +1 -0
  794. package/dist/trusted-proxy-d4Tk6biT.mjs +51 -0
  795. package/dist/trusted-proxy-d4Tk6biT.mjs.map +1 -0
  796. package/dist/turnstile-DaFLtyrs.mjs +58 -0
  797. package/dist/turnstile-DaFLtyrs.mjs.map +1 -0
  798. package/dist/types-B4-1P2DK.d.mts +227 -0
  799. package/dist/types-B4-1P2DK.d.mts.map +1 -0
  800. package/dist/types-BKXA0f-c.mjs +16 -0
  801. package/dist/types-BKXA0f-c.mjs.map +1 -0
  802. package/dist/types-BiHcpUoO.d.mts +374 -0
  803. package/dist/types-BiHcpUoO.d.mts.map +1 -0
  804. package/dist/types-BihhvkyN.d.mts +85 -0
  805. package/dist/types-BihhvkyN.d.mts.map +1 -0
  806. package/dist/types-Bq9Wohd7.d.mts +363 -0
  807. package/dist/types-Bq9Wohd7.d.mts.map +1 -0
  808. package/dist/types-C-Xp0CcN.mjs +103 -0
  809. package/dist/types-C-Xp0CcN.mjs.map +1 -0
  810. package/dist/types-C4P7F1Vq.d.mts +1741 -0
  811. package/dist/types-C4P7F1Vq.d.mts.map +1 -0
  812. package/dist/types-CGaqwHw4.mjs +37 -0
  813. package/dist/types-CGaqwHw4.mjs.map +1 -0
  814. package/dist/types-Cj0_yZkn.mjs +18 -0
  815. package/dist/types-Cj0_yZkn.mjs.map +1 -0
  816. package/dist/types-D4ECCE4X.mjs +77 -0
  817. package/dist/types-D4ECCE4X.mjs.map +1 -0
  818. package/dist/types-DcRJp0vS.d.mts +125 -0
  819. package/dist/types-DcRJp0vS.d.mts.map +1 -0
  820. package/dist/types-Ff85fTzU.d.mts +289 -0
  821. package/dist/types-Ff85fTzU.d.mts.map +1 -0
  822. package/dist/types-UKr5Sf4y.d.mts +916 -0
  823. package/dist/types-UKr5Sf4y.d.mts.map +1 -0
  824. package/dist/types-YpN8hZW7.d.mts +204 -0
  825. package/dist/types-YpN8hZW7.d.mts.map +1 -0
  826. package/dist/update-check-DrlsK-tt.mjs +161 -0
  827. package/dist/update-check-DrlsK-tt.mjs.map +1 -0
  828. package/dist/upload-attempts-D8CPPvRU.mjs +32 -0
  829. package/dist/upload-attempts-D8CPPvRU.mjs.map +1 -0
  830. package/dist/url-pattern-Di4Nn3vP.mjs +41 -0
  831. package/dist/url-pattern-Di4Nn3vP.mjs.map +1 -0
  832. package/dist/user-B-ZdGjhI.mjs +171 -0
  833. package/dist/user-B-ZdGjhI.mjs.map +1 -0
  834. package/dist/utils-D7wL3r6-.mjs +437 -0
  835. package/dist/utils-D7wL3r6-.mjs.map +1 -0
  836. package/dist/validate-BSZbSUDk.mjs +524 -0
  837. package/dist/validate-BSZbSUDk.mjs.map +1 -0
  838. package/dist/validate-Dfv0DpZi.d.mts +468 -0
  839. package/dist/validate-Dfv0DpZi.d.mts.map +1 -0
  840. package/dist/validation-DRyC_Vm5.mjs +219 -0
  841. package/dist/validation-DRyC_Vm5.mjs.map +1 -0
  842. package/dist/version-C4kz0PcO.mjs +7 -0
  843. package/dist/version-C4kz0PcO.mjs.map +1 -0
  844. package/dist/version-jhs2BRtl.mjs +9 -0
  845. package/dist/version-jhs2BRtl.mjs.map +1 -0
  846. package/dist/video-DReZ48Un.mjs +54 -0
  847. package/dist/video-DReZ48Un.mjs.map +1 -0
  848. package/dist/virtual-modules-D0e6VbEv.mjs +516 -0
  849. package/dist/virtual-modules-D0e6VbEv.mjs.map +1 -0
  850. package/dist/widgets-BkcMzKct.mjs +121 -0
  851. package/dist/widgets-BkcMzKct.mjs.map +1 -0
  852. package/dist/wxr-taxonomies-BxI0CbTg.mjs +453 -0
  853. package/dist/wxr-taxonomies-BxI0CbTg.mjs.map +1 -0
  854. package/dist/wxr-taxonomies-Dad33GJi.d.mts +34 -0
  855. package/dist/wxr-taxonomies-Dad33GJi.d.mts.map +1 -0
  856. package/dist/zod-generator-BQilAboC.mjs +424 -0
  857. package/dist/zod-generator-BQilAboC.mjs.map +1 -0
  858. package/package.json +19 -10
  859. package/src/api/errors.ts +12 -0
  860. package/src/api/handlers/api-tokens.ts +12 -6
  861. package/src/api/handlers/bylines.ts +24 -3
  862. package/src/api/handlers/calendar.ts +246 -0
  863. package/src/api/handlers/content.ts +69 -13
  864. package/src/api/handlers/domain-move-notice.ts +94 -0
  865. package/src/api/handlers/index.ts +2 -0
  866. package/src/api/handlers/manifest.ts +5 -0
  867. package/src/api/handlers/media-allowlist.ts +1 -0
  868. package/src/api/handlers/media-upload.ts +13 -10
  869. package/src/api/handlers/media-usage.ts +2 -1
  870. package/src/api/handlers/media.ts +1 -0
  871. package/src/api/handlers/plugins.ts +21 -29
  872. package/src/api/handlers/redirects.ts +6 -6
  873. package/src/api/handlers/seo.ts +155 -59
  874. package/src/api/handlers/settings.ts +31 -1
  875. package/src/api/handlers/site-domain.ts +143 -0
  876. package/src/api/openapi/document.ts +2 -2
  877. package/src/api/public-url.ts +1 -1
  878. package/src/api/schemas/bylines.ts +3 -1
  879. package/src/api/schemas/content.ts +36 -0
  880. package/src/api/schemas/media-usage.ts +7 -1
  881. package/src/api/schemas/media.ts +12 -0
  882. package/src/api/site-url.ts +53 -5
  883. package/src/api/types.ts +2 -0
  884. package/src/astro/image-endpoint.ts +46 -4
  885. package/src/astro/integration/admin-locales.ts +168 -0
  886. package/src/astro/integration/dev-env.ts +84 -0
  887. package/src/astro/integration/index.ts +67 -10
  888. package/src/astro/integration/routes.ts +25 -0
  889. package/src/astro/integration/runtime.ts +15 -0
  890. package/src/astro/integration/vite-config.ts +29 -1
  891. package/src/astro/middleware/auth.ts +16 -3
  892. package/src/astro/middleware/csp.ts +2 -0
  893. package/src/astro/middleware/redirect.ts +47 -21
  894. package/src/astro/middleware/request-context.ts +64 -31
  895. package/src/astro/middleware.ts +150 -26
  896. package/src/astro/prefetch.ts +10 -3
  897. package/src/astro/routes/api/admin/bylines/[id]/translations.ts +0 -5
  898. package/src/astro/routes/api/admin/bylines/index.ts +1 -1
  899. package/src/astro/routes/api/admin/plugins/[id]/index.ts +1 -0
  900. package/src/astro/routes/api/auth/handover.ts +58 -0
  901. package/src/astro/routes/api/auth/magic-link/verify.ts +11 -3
  902. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +56 -4
  903. package/src/astro/routes/api/auth/oauth/[provider].ts +37 -4
  904. package/src/astro/routes/api/auth/passkey/options.ts +10 -2
  905. package/src/astro/routes/api/calendar.ts +35 -0
  906. package/src/astro/routes/api/import/wordpress/execute.ts +19 -0
  907. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +80 -42
  908. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +15 -0
  909. package/src/astro/routes/api/manifest.ts +10 -0
  910. package/src/astro/routes/api/media/[id]/confirm.ts +19 -0
  911. package/src/astro/routes/api/media/file/[...key].ts +60 -12
  912. package/src/astro/routes/api/media/upload-url.ts +35 -7
  913. package/src/astro/routes/api/media.ts +24 -5
  914. package/src/astro/routes/api/schema/index.ts +1 -1
  915. package/src/astro/routes/api/settings/domain-notify.ts +76 -0
  916. package/src/astro/routes/api/settings/domain.ts +63 -0
  917. package/src/astro/routes/api/setup/index.ts +21 -2
  918. package/src/astro/routes/api/site/domain-proof.ts +28 -0
  919. package/src/astro/routes/sitemap-[collection].xml.ts +31 -8
  920. package/src/astro/routes/sitemap.xml.ts +12 -10
  921. package/src/astro/types.ts +13 -1
  922. package/src/auth/providers/microsoft-admin.tsx +31 -0
  923. package/src/auth/providers/microsoft.ts +47 -0
  924. package/src/auth/rate-limit.ts +18 -13
  925. package/src/auth/types.ts +7 -0
  926. package/src/aws-sdk.d.ts +2 -1
  927. package/src/cleanup.ts +26 -0
  928. package/src/cli/commands/export-seed.ts +2 -0
  929. package/src/cli/commands/seed.ts +5 -4
  930. package/src/client/index.ts +6 -1
  931. package/src/comments/public-submission.ts +15 -8
  932. package/src/comments/turnstile.ts +6 -1
  933. package/src/components/CommentForm.astro +48 -8
  934. package/src/components/Comments.astro +50 -28
  935. package/src/components/EmDashImage.astro +6 -4
  936. package/src/components/Gallery.astro +4 -3
  937. package/src/components/HtmlBlock.astro +17 -6
  938. package/src/components/HtmlBlockFrame.astro +63 -0
  939. package/src/components/Iframe.astro +141 -0
  940. package/src/components/Image.astro +4 -3
  941. package/src/components/InlinePortableTextEditor.tsx +156 -33
  942. package/src/components/Video.astro +100 -0
  943. package/src/components/WebMcpSearch.astro +80 -0
  944. package/src/components/index.ts +8 -2
  945. package/src/components/inline-code-block.tsx +8 -0
  946. package/src/components/webmcp-search.ts +138 -0
  947. package/src/components/widgets/RecentPosts.astro +57 -17
  948. package/src/config/secrets.ts +27 -15
  949. package/src/content/converters/mark-safety.ts +3 -3
  950. package/src/content/converters/portable-text-identity.ts +2 -0
  951. package/src/content/converters/portable-text-to-prosemirror.ts +43 -2
  952. package/src/content/converters/prosemirror-to-portable-text.ts +44 -2
  953. package/src/content/converters/types.ts +46 -0
  954. package/src/content/converters/video.ts +77 -0
  955. package/src/database/content-datetime.ts +11 -4
  956. package/src/database/datetime-storage.ts +1 -1
  957. package/src/database/migrations/090_redirect_enable_loop_guard.ts +70 -0
  958. package/src/database/migrations/091_redirect_artifacts.ts +141 -0
  959. package/src/database/migrations/runner.ts +17 -1
  960. package/src/database/repositories/comment.ts +17 -3
  961. package/src/database/repositories/content.ts +103 -29
  962. package/src/database/repositories/plugin-storage.ts +28 -19
  963. package/src/database/repositories/redirect.ts +30 -3
  964. package/src/database/repositories/taxonomy.ts +61 -46
  965. package/src/database/repositories/types.ts +2 -0
  966. package/src/database/request-scoped.ts +34 -0
  967. package/src/database/types.ts +23 -0
  968. package/src/db/adapters.ts +2 -0
  969. package/src/emdash-runtime.ts +245 -71
  970. package/src/import/menus.ts +3 -0
  971. package/src/import/sources/wordpress-plugin.ts +3 -2
  972. package/src/import/utils.ts +2 -3
  973. package/src/index.ts +4 -0
  974. package/src/loader.ts +360 -98
  975. package/src/mcp/server.ts +12 -4
  976. package/src/media/image-endpoint.ts +101 -1
  977. package/src/media/mime.ts +21 -0
  978. package/src/media/responsive.ts +14 -7
  979. package/src/media/usage/extractor.ts +19 -2
  980. package/src/media/usage/types.ts +5 -1
  981. package/src/menus/index.ts +30 -26
  982. package/src/plugin-utils.ts +1 -1
  983. package/src/plugins/context.ts +3 -7
  984. package/src/plugins/hooks.ts +11 -1
  985. package/src/plugins/http-route-dispatch.ts +5 -1
  986. package/src/plugins/manager.ts +12 -2
  987. package/src/plugins/manifest-schema.ts +1 -0
  988. package/src/plugins/route-error.ts +14 -0
  989. package/src/plugins/routes.ts +2 -2
  990. package/src/plugins/state.ts +36 -0
  991. package/src/plugins/types.ts +22 -0
  992. package/src/query.ts +20 -29
  993. package/src/redirects/artifacts.ts +460 -0
  994. package/src/redirects/cache.ts +76 -41
  995. package/src/redirects/patterns.ts +26 -2
  996. package/src/request-context.ts +1 -1
  997. package/src/schema/registry.ts +7 -4
  998. package/src/schema/slug.ts +19 -0
  999. package/src/search/text-extraction.ts +6 -1
  1000. package/src/seed/apply.ts +65 -57
  1001. package/src/seed/validate.ts +31 -0
  1002. package/src/seo/sitemap-name.ts +22 -0
  1003. package/src/settings/index.ts +17 -12
  1004. package/src/storage/index.ts +2 -0
  1005. package/src/storage/local.ts +10 -2
  1006. package/src/storage/range.ts +54 -0
  1007. package/src/storage/s3.ts +52 -8
  1008. package/src/storage/types.ts +30 -3
  1009. package/src/transfer/format/columns.ts +3 -0
  1010. package/src/transfer/import/rebuild.ts +17 -2
  1011. package/src/transfer/import/scaffold.ts +16 -9
  1012. package/src/ui.ts +2 -0
  1013. package/src/utils/sanitize.ts +14 -9
  1014. package/src/utils/slugify.ts +6 -1
  1015. package/src/virtual-modules.d.ts +3 -0
  1016. package/src/visual-editing/toolbar.ts +80 -35
  1017. package/src/widgets/components.ts +4 -0
  1018. package/src/widgets/index.ts +74 -82
  1019. package/src/widgets/recent-posts.ts +62 -0
  1020. package/dist/action-token-D2KE1Rqf.mjs +0 -26
  1021. package/dist/action-token-D2KE1Rqf.mjs.map +0 -1
  1022. package/dist/activation-DDuqAat2.mjs +0 -387
  1023. package/dist/activation-DDuqAat2.mjs.map +0 -1
  1024. package/dist/adapters-Ds-1Pmkm.d.mts +0 -171
  1025. package/dist/adapters-Ds-1Pmkm.d.mts.map +0 -1
  1026. package/dist/allowed-origins-D78QvbTO.mjs +0 -116
  1027. package/dist/allowed-origins-D78QvbTO.mjs.map +0 -1
  1028. package/dist/api-BBZlif0y.mjs +0 -4974
  1029. package/dist/api-BBZlif0y.mjs.map +0 -1
  1030. package/dist/api-tokens-BGbfWZVO.mjs +0 -166
  1031. package/dist/api-tokens-BGbfWZVO.mjs.map +0 -1
  1032. package/dist/apply-CDNV4JJ-.mjs +0 -1672
  1033. package/dist/apply-CDNV4JJ-.mjs.map +0 -1
  1034. package/dist/approvals-CVvJSEBF.mjs +0 -460
  1035. package/dist/approvals-CVvJSEBF.mjs.map +0 -1
  1036. package/dist/artifact-fetch-CGK0DQLj.mjs +0 -659
  1037. package/dist/artifact-fetch-CGK0DQLj.mjs.map +0 -1
  1038. package/dist/audit-BszbE_BP.mjs +0 -127
  1039. package/dist/audit-BszbE_BP.mjs.map +0 -1
  1040. package/dist/authoritative-records-ChM8dTeV.mjs +0 -5172
  1041. package/dist/authoritative-records-ChM8dTeV.mjs.map +0 -1
  1042. package/dist/authorize-CTGywswM.mjs +0 -49
  1043. package/dist/authorize-CTGywswM.mjs.map +0 -1
  1044. package/dist/backup-CDrPP8fI.mjs +0 -246
  1045. package/dist/backup-CDrPP8fI.mjs.map +0 -1
  1046. package/dist/base64-DvnNgP7e.mjs +0 -63
  1047. package/dist/base64-DvnNgP7e.mjs.map +0 -1
  1048. package/dist/block-types-nNEaPkvx.mjs +0 -132
  1049. package/dist/block-types-nNEaPkvx.mjs.map +0 -1
  1050. package/dist/block-values-Bho1nsZF.mjs +0 -175
  1051. package/dist/block-values-Bho1nsZF.mjs.map +0 -1
  1052. package/dist/byline-02tu_Uc-.mjs +0 -909
  1053. package/dist/byline-02tu_Uc-.mjs.map +0 -1
  1054. package/dist/byline-fields-BYbTptTU.mjs +0 -280
  1055. package/dist/byline-fields-BYbTptTU.mjs.map +0 -1
  1056. package/dist/byline-fields-BjQH-1z9.mjs +0 -123
  1057. package/dist/byline-fields-BjQH-1z9.mjs.map +0 -1
  1058. package/dist/bylines-VytjeHBI.mjs +0 -214
  1059. package/dist/bylines-VytjeHBI.mjs.map +0 -1
  1060. package/dist/bylines-gyWBWUUc.mjs +0 -150
  1061. package/dist/bylines-gyWBWUUc.mjs.map +0 -1
  1062. package/dist/cache-CPanL9IO.mjs +0 -114
  1063. package/dist/cache-CPanL9IO.mjs.map +0 -1
  1064. package/dist/canonical-CAecl1UQ.mjs +0 -174
  1065. package/dist/canonical-CAecl1UQ.mjs.map +0 -1
  1066. package/dist/capture-triggers-CdZfvHtF.mjs +0 -362
  1067. package/dist/capture-triggers-CdZfvHtF.mjs.map +0 -1
  1068. package/dist/challenge-store-VePApay4.mjs +0 -51
  1069. package/dist/challenge-store-VePApay4.mjs.map +0 -1
  1070. package/dist/checksum-BSIhEAYW.mjs +0 -162
  1071. package/dist/checksum-BSIhEAYW.mjs.map +0 -1
  1072. package/dist/chrome-tags-CKELm-bo.mjs +0 -24
  1073. package/dist/chrome-tags-CKELm-bo.mjs.map +0 -1
  1074. package/dist/chunks-DBmkKmBH.mjs +0 -25
  1075. package/dist/chunks-DBmkKmBH.mjs.map +0 -1
  1076. package/dist/collection-slugs-cache-BNRAPNiu.mjs +0 -85
  1077. package/dist/collection-slugs-cache-BNRAPNiu.mjs.map +0 -1
  1078. package/dist/comment-Cq1UYN8_.mjs +0 -313
  1079. package/dist/comment-Cq1UYN8_.mjs.map +0 -1
  1080. package/dist/comment-reaction-B5sZzSoA.mjs +0 -85
  1081. package/dist/comment-reaction-B5sZzSoA.mjs.map +0 -1
  1082. package/dist/comments-B3F-ETyh.mjs +0 -195
  1083. package/dist/comments-B3F-ETyh.mjs.map +0 -1
  1084. package/dist/comments-zrTgWBSg.mjs +0 -99
  1085. package/dist/comments-zrTgWBSg.mjs.map +0 -1
  1086. package/dist/components-Dbf4Eq16.mjs +0 -108
  1087. package/dist/components-Dbf4Eq16.mjs.map +0 -1
  1088. package/dist/config-BCM5sQP7.d.mts +0 -6688
  1089. package/dist/config-BCM5sQP7.d.mts.map +0 -1
  1090. package/dist/config-CXPcy2iY.mjs +0 -228
  1091. package/dist/config-CXPcy2iY.mjs.map +0 -1
  1092. package/dist/config-CZkZBuBK.d.mts +0 -39
  1093. package/dist/config-CZkZBuBK.d.mts.map +0 -1
  1094. package/dist/connection-C5GIciX9.mjs +0 -36
  1095. package/dist/connection-C5GIciX9.mjs.map +0 -1
  1096. package/dist/content-RzEXY3QX.mjs +0 -2020
  1097. package/dist/content-RzEXY3QX.mjs.map +0 -1
  1098. package/dist/content-list-query-B6P37Ug_.d.mts +0 -26
  1099. package/dist/content-list-query-B6P37Ug_.d.mts.map +0 -1
  1100. package/dist/content-repair-BDVsYXWK.mjs +0 -442
  1101. package/dist/content-repair-BDVsYXWK.mjs.map +0 -1
  1102. package/dist/context-BpTIzY5h.mjs +0 -2026
  1103. package/dist/context-BpTIzY5h.mjs.map +0 -1
  1104. package/dist/credits-JOE92Gr7.mjs +0 -110
  1105. package/dist/credits-JOE92Gr7.mjs.map +0 -1
  1106. package/dist/cron-ktyapbdB.mjs +0 -306
  1107. package/dist/cron-ktyapbdB.mjs.map +0 -1
  1108. package/dist/dashboard-BoV4pwPP.mjs +0 -151
  1109. package/dist/dashboard-BoV4pwPP.mjs.map +0 -1
  1110. package/dist/default-B7KnYwtQ.mjs +0 -81
  1111. package/dist/default-B7KnYwtQ.mjs.map +0 -1
  1112. package/dist/deferred-tasks-eaUIqXj8.d.mts +0 -9
  1113. package/dist/deferred-tasks-eaUIqXj8.d.mts.map +0 -1
  1114. package/dist/dev-typegen-8JlYYFno.mjs +0 -29
  1115. package/dist/dev-typegen-8JlYYFno.mjs.map +0 -1
  1116. package/dist/device-flow-BlV_z24g.mjs +0 -521
  1117. package/dist/device-flow-BlV_z24g.mjs.map +0 -1
  1118. package/dist/digest-DiYAoZpf.mjs +0 -194
  1119. package/dist/digest-DiYAoZpf.mjs.map +0 -1
  1120. package/dist/direct-executor-Db8xA3zg.mjs +0 -104
  1121. package/dist/direct-executor-Db8xA3zg.mjs.map +0 -1
  1122. package/dist/email-console-Dm78wSmg.mjs +0 -50
  1123. package/dist/email-console-Dm78wSmg.mjs.map +0 -1
  1124. package/dist/email-locale-B4X9LXe9.mjs +0 -25
  1125. package/dist/email-locale-B4X9LXe9.mjs.map +0 -1
  1126. package/dist/emdash-runtime-BakQ6io7.mjs +0 -4239
  1127. package/dist/emdash-runtime-BakQ6io7.mjs.map +0 -1
  1128. package/dist/enrich-BDdVNeDK.mjs +0 -194
  1129. package/dist/enrich-BDdVNeDK.mjs.map +0 -1
  1130. package/dist/entry-lock-DY8B-xcr.d.mts +0 -4544
  1131. package/dist/entry-lock-DY8B-xcr.d.mts.map +0 -1
  1132. package/dist/entry-lock-DrUAdTzf.mjs +0 -163
  1133. package/dist/entry-lock-DrUAdTzf.mjs.map +0 -1
  1134. package/dist/entry-lock-yIkBduqJ.mjs +0 -1180
  1135. package/dist/entry-lock-yIkBduqJ.mjs.map +0 -1
  1136. package/dist/env-types-DkHYR2pl.mjs +0 -38
  1137. package/dist/env-types-DkHYR2pl.mjs.map +0 -1
  1138. package/dist/error-DIYXAVk4.mjs +0 -90
  1139. package/dist/error-DIYXAVk4.mjs.map +0 -1
  1140. package/dist/errors-CNvATh_7.mjs +0 -469
  1141. package/dist/errors-CNvATh_7.mjs.map +0 -1
  1142. package/dist/errors-CjXd1LgH.mjs +0 -120
  1143. package/dist/errors-CjXd1LgH.mjs.map +0 -1
  1144. package/dist/escape-B1GM0yJU.mjs +0 -9
  1145. package/dist/escape-B1GM0yJU.mjs.map +0 -1
  1146. package/dist/fence-8MaKKWhp.mjs +0 -162
  1147. package/dist/fence-8MaKKWhp.mjs.map +0 -1
  1148. package/dist/field-defs-cache-DEkFYCDE.mjs +0 -77
  1149. package/dist/field-defs-cache-DEkFYCDE.mjs.map +0 -1
  1150. package/dist/field-map-D7ioN0r0.mjs +0 -83
  1151. package/dist/field-map-D7ioN0r0.mjs.map +0 -1
  1152. package/dist/fts-manager-DU03MxWu.mjs +0 -443
  1153. package/dist/fts-manager-DU03MxWu.mjs.map +0 -1
  1154. package/dist/gc-DMiE7ezk.mjs +0 -75
  1155. package/dist/gc-DMiE7ezk.mjs.map +0 -1
  1156. package/dist/hash-CY4QdeMJ.mjs +0 -37
  1157. package/dist/hash-CY4QdeMJ.mjs.map +0 -1
  1158. package/dist/hook-result-Cb67D3P3.d.mts +0 -31
  1159. package/dist/hook-result-Cb67D3P3.d.mts.map +0 -1
  1160. package/dist/hreflang-Bterc3b7.mjs +0 -105
  1161. package/dist/hreflang-Bterc3b7.mjs.map +0 -1
  1162. package/dist/hreflang-uIBOE5yg.d.mts +0 -50
  1163. package/dist/hreflang-uIBOE5yg.d.mts.map +0 -1
  1164. package/dist/http-route-dispatch-DubjTibD.mjs +0 -339
  1165. package/dist/http-route-dispatch-DubjTibD.mjs.map +0 -1
  1166. package/dist/identity-DJ-5AcdG.mjs +0 -35
  1167. package/dist/identity-DJ-5AcdG.mjs.map +0 -1
  1168. package/dist/import-DEfuIgRz.mjs +0 -2020
  1169. package/dist/import-DEfuIgRz.mjs.map +0 -1
  1170. package/dist/index-xCy3j0f6.d.mts +0 -623
  1171. package/dist/index-xCy3j0f6.d.mts.map +0 -1
  1172. package/dist/init-lock-4eRRB4Le.mjs +0 -56
  1173. package/dist/init-lock-4eRRB4Le.mjs.map +0 -1
  1174. package/dist/install-finalization-DeGRbaVa.mjs +0 -58
  1175. package/dist/install-finalization-DeGRbaVa.mjs.map +0 -1
  1176. package/dist/keys-DWwMxdZh.mjs +0 -61
  1177. package/dist/keys-DWwMxdZh.mjs.map +0 -1
  1178. package/dist/kinds-D1A-00-F.mjs +0 -974
  1179. package/dist/kinds-D1A-00-F.mjs.map +0 -1
  1180. package/dist/lifecycle-BWtBGNEn.mjs +0 -25
  1181. package/dist/lifecycle-BWtBGNEn.mjs.map +0 -1
  1182. package/dist/load-BR1NEa_3.mjs +0 -28
  1183. package/dist/load-BR1NEa_3.mjs.map +0 -1
  1184. package/dist/loader-D4edocWH.mjs +0 -1197
  1185. package/dist/loader-D4edocWH.mjs.map +0 -1
  1186. package/dist/loops-DLaHFrvI.mjs +0 -205
  1187. package/dist/loops-DLaHFrvI.mjs.map +0 -1
  1188. package/dist/manifest-builder-8jsSLR7s.mjs +0 -189
  1189. package/dist/manifest-builder-8jsSLR7s.mjs.map +0 -1
  1190. package/dist/manifest-schema-DcZjRNEv.mjs +0 -496
  1191. package/dist/manifest-schema-DcZjRNEv.mjs.map +0 -1
  1192. package/dist/marketplace-CNxjq10S.mjs +0 -257
  1193. package/dist/marketplace-CNxjq10S.mjs.map +0 -1
  1194. package/dist/media-3v7mqAVX.mjs +0 -294
  1195. package/dist/media-3v7mqAVX.mjs.map +0 -1
  1196. package/dist/media-Bs6qmlVl.mjs +0 -408
  1197. package/dist/media-Bs6qmlVl.mjs.map +0 -1
  1198. package/dist/media-CputjGE1.mjs +0 -373
  1199. package/dist/media-CputjGE1.mjs.map +0 -1
  1200. package/dist/media-allowlist-isqF1gAh.mjs +0 -44
  1201. package/dist/media-allowlist-isqF1gAh.mjs.map +0 -1
  1202. package/dist/media-folders-D2VdVBf6.mjs +0 -210
  1203. package/dist/media-folders-D2VdVBf6.mjs.map +0 -1
  1204. package/dist/media-url-DbkMY3y6.mjs +0 -43
  1205. package/dist/media-url-DbkMY3y6.mjs.map +0 -1
  1206. package/dist/media-usage-activation-BaT4AlNI.mjs +0 -78
  1207. package/dist/media-usage-activation-BaT4AlNI.mjs.map +0 -1
  1208. package/dist/media-usage-v43L0kR_.mjs +0 -282
  1209. package/dist/media-usage-v43L0kR_.mjs.map +0 -1
  1210. package/dist/media-usage-work-CY8gIeU-.mjs +0 -151
  1211. package/dist/media-usage-work-CY8gIeU-.mjs.map +0 -1
  1212. package/dist/media-usage-work-VO108Q9E.mjs +0 -1015
  1213. package/dist/media-usage-work-VO108Q9E.mjs.map +0 -1
  1214. package/dist/menus-BYyeRemR.mjs +0 -3647
  1215. package/dist/menus-BYyeRemR.mjs.map +0 -1
  1216. package/dist/menus-mC_XTxD7.mjs +0 -92
  1217. package/dist/menus-mC_XTxD7.mjs.map +0 -1
  1218. package/dist/menus-trvc6_fW.mjs +0 -753
  1219. package/dist/menus-trvc6_fW.mjs.map +0 -1
  1220. package/dist/mime-IZf-LHIS.mjs +0 -36
  1221. package/dist/mime-IZf-LHIS.mjs.map +0 -1
  1222. package/dist/mode-B9-gMXL9.mjs +0 -23
  1223. package/dist/mode-B9-gMXL9.mjs.map +0 -1
  1224. package/dist/node-sqlite-compat-DztcWs4y.mjs +0 -63
  1225. package/dist/node-sqlite-compat-DztcWs4y.mjs.map +0 -1
  1226. package/dist/oauth-authorization-BIHy9SGU.mjs +0 -274
  1227. package/dist/oauth-authorization-BIHy9SGU.mjs.map +0 -1
  1228. package/dist/oauth-clients-BZWZvKXa.mjs +0 -266
  1229. package/dist/oauth-clients-BZWZvKXa.mjs.map +0 -1
  1230. package/dist/oauth-state-store-CxXTjXdI.mjs +0 -50
  1231. package/dist/oauth-state-store-CxXTjXdI.mjs.map +0 -1
  1232. package/dist/oauth-user-lookup-GWojfxRl.mjs +0 -26
  1233. package/dist/oauth-user-lookup-GWojfxRl.mjs.map +0 -1
  1234. package/dist/options-DRF6dlXY.d.mts +0 -81
  1235. package/dist/options-DRF6dlXY.d.mts.map +0 -1
  1236. package/dist/ownership-Bd77AmJz.mjs +0 -12
  1237. package/dist/ownership-Bd77AmJz.mjs.map +0 -1
  1238. package/dist/parse-5Y0W39xR.mjs +0 -81
  1239. package/dist/parse-5Y0W39xR.mjs.map +0 -1
  1240. package/dist/parse-BPblgoa-.d.mts +0 -172
  1241. package/dist/parse-BPblgoa-.d.mts.map +0 -1
  1242. package/dist/passkey-config-PiQ5cBnt.mjs +0 -46
  1243. package/dist/passkey-config-PiQ5cBnt.mjs.map +0 -1
  1244. package/dist/paths-DkS74lBM.mjs +0 -77
  1245. package/dist/paths-DkS74lBM.mjs.map +0 -1
  1246. package/dist/patterns-DyZJ67I7.mjs +0 -140
  1247. package/dist/patterns-DyZJ67I7.mjs.map +0 -1
  1248. package/dist/placeholder-Bez2282x.d.mts +0 -306
  1249. package/dist/placeholder-Bez2282x.d.mts.map +0 -1
  1250. package/dist/plan-Lw7iGnGD.mjs +0 -135
  1251. package/dist/plan-Lw7iGnGD.mjs.map +0 -1
  1252. package/dist/plugins-D7xSh1Lu.mjs +0 -277
  1253. package/dist/plugins-D7xSh1Lu.mjs.map +0 -1
  1254. package/dist/policy-DMRiMdLn.mjs +0 -45
  1255. package/dist/policy-DMRiMdLn.mjs.map +0 -1
  1256. package/dist/portable-text-kcyN3F-a.mjs +0 -307
  1257. package/dist/portable-text-kcyN3F-a.mjs.map +0 -1
  1258. package/dist/preview--2VDLaND.mjs +0 -107
  1259. package/dist/preview--2VDLaND.mjs.map +0 -1
  1260. package/dist/protocol-FvwLA8P-.d.mts +0 -61
  1261. package/dist/protocol-FvwLA8P-.d.mts.map +0 -1
  1262. package/dist/public-submission-BRyK76U8.mjs +0 -250
  1263. package/dist/public-submission-BRyK76U8.mjs.map +0 -1
  1264. package/dist/public-url-7j0cpCFc.mjs +0 -101
  1265. package/dist/public-url-7j0cpCFc.mjs.map +0 -1
  1266. package/dist/query-BRNTde3V.mjs +0 -41
  1267. package/dist/query-BRNTde3V.mjs.map +0 -1
  1268. package/dist/query-DyYYWOKk.mjs +0 -1161
  1269. package/dist/query-DyYYWOKk.mjs.map +0 -1
  1270. package/dist/rate-limit-D0BcE76H.mjs +0 -120
  1271. package/dist/rate-limit-D0BcE76H.mjs.map +0 -1
  1272. package/dist/receipt-C4icyHlC.mjs +0 -43
  1273. package/dist/receipt-C4icyHlC.mjs.map +0 -1
  1274. package/dist/redirect-9DbtxSNG.mjs +0 -19
  1275. package/dist/redirect-9DbtxSNG.mjs.map +0 -1
  1276. package/dist/redirect-BvFKh8Sz.mjs +0 -416
  1277. package/dist/redirect-BvFKh8Sz.mjs.map +0 -1
  1278. package/dist/redirects-1nhdEaTq.mjs +0 -148
  1279. package/dist/redirects-1nhdEaTq.mjs.map +0 -1
  1280. package/dist/redirects-D1yMnSR9.mjs +0 -446
  1281. package/dist/redirects-D1yMnSR9.mjs.map +0 -1
  1282. package/dist/registry-CzPJNu3H.mjs +0 -8366
  1283. package/dist/registry-CzPJNu3H.mjs.map +0 -1
  1284. package/dist/relations-C8JpSkU6.mjs +0 -776
  1285. package/dist/relations-C8JpSkU6.mjs.map +0 -1
  1286. package/dist/request-meta-Bs60XW1m.mjs +0 -141
  1287. package/dist/request-meta-Bs60XW1m.mjs.map +0 -1
  1288. package/dist/resolve-BHWgMU_X.mjs +0 -100
  1289. package/dist/resolve-BHWgMU_X.mjs.map +0 -1
  1290. package/dist/revision-CGkQA0e5.mjs +0 -366
  1291. package/dist/revision-CGkQA0e5.mjs.map +0 -1
  1292. package/dist/route-wire-ClUVQduA.mjs +0 -197
  1293. package/dist/route-wire-ClUVQduA.mjs.map +0 -1
  1294. package/dist/routes-D2GFl3B7.mjs +0 -272
  1295. package/dist/routes-D2GFl3B7.mjs.map +0 -1
  1296. package/dist/runner-CLpxmhl8.mjs +0 -6197
  1297. package/dist/runner-CLpxmhl8.mjs.map +0 -1
  1298. package/dist/runner-CWl76CH3.d.mts +0 -68
  1299. package/dist/runner-CWl76CH3.d.mts.map +0 -1
  1300. package/dist/schema-27XefSYG.mjs +0 -13
  1301. package/dist/schema-27XefSYG.mjs.map +0 -1
  1302. package/dist/schema-Btvj3BMh.mjs +0 -815
  1303. package/dist/schema-Btvj3BMh.mjs.map +0 -1
  1304. package/dist/scopes-DGCj6K4p.mjs +0 -34
  1305. package/dist/scopes-DGCj6K4p.mjs.map +0 -1
  1306. package/dist/search-pZRRmWjh.mjs +0 -448
  1307. package/dist/search-pZRRmWjh.mjs.map +0 -1
  1308. package/dist/secrets-CIi0nFiR.mjs +0 -341
  1309. package/dist/secrets-CIi0nFiR.mjs.map +0 -1
  1310. package/dist/sections-CIhqs3Ao.mjs +0 -346
  1311. package/dist/sections-CIhqs3Ao.mjs.map +0 -1
  1312. package/dist/seo-DifeiGdl.mjs +0 -114
  1313. package/dist/seo-DifeiGdl.mjs.map +0 -1
  1314. package/dist/seo-DpYxmGaL.mjs +0 -133
  1315. package/dist/seo-DpYxmGaL.mjs.map +0 -1
  1316. package/dist/service-COVdVpSK.mjs +0 -150
  1317. package/dist/service-COVdVpSK.mjs.map +0 -1
  1318. package/dist/session-user-C6IEjuty.mjs +0 -61
  1319. package/dist/session-user-C6IEjuty.mjs.map +0 -1
  1320. package/dist/settings-CwcrX-Ay.mjs +0 -61
  1321. package/dist/settings-CwcrX-Ay.mjs.map +0 -1
  1322. package/dist/settings-D_cJtT48.mjs +0 -439
  1323. package/dist/settings-D_cJtT48.mjs.map +0 -1
  1324. package/dist/settings-tnPQN3Z2.mjs +0 -51
  1325. package/dist/settings-tnPQN3Z2.mjs.map +0 -1
  1326. package/dist/setup-complete-BOrI10UY.mjs +0 -65
  1327. package/dist/setup-complete-BOrI10UY.mjs.map +0 -1
  1328. package/dist/setup-nonce-0XTBLTsX.mjs +0 -25
  1329. package/dist/setup-nonce-0XTBLTsX.mjs.map +0 -1
  1330. package/dist/single-flight-cache-B5O2qhO1.mjs +0 -104
  1331. package/dist/single-flight-cache-B5O2qhO1.mjs.map +0 -1
  1332. package/dist/site-settings-B7vGFRDT.mjs +0 -50
  1333. package/dist/site-settings-B7vGFRDT.mjs.map +0 -1
  1334. package/dist/site-url-wEYRMHBv.mjs +0 -16
  1335. package/dist/site-url-wEYRMHBv.mjs.map +0 -1
  1336. package/dist/slugify-T5GHH42D.mjs +0 -17
  1337. package/dist/slugify-T5GHH42D.mjs.map +0 -1
  1338. package/dist/snapshot-W36KbLmc.mjs +0 -414
  1339. package/dist/snapshot-W36KbLmc.mjs.map +0 -1
  1340. package/dist/ssrf-CRJ-xom5.mjs +0 -406
  1341. package/dist/ssrf-CRJ-xom5.mjs.map +0 -1
  1342. package/dist/stage-DA0NrTRZ.mjs +0 -662
  1343. package/dist/stage-DA0NrTRZ.mjs.map +0 -1
  1344. package/dist/staged-Bw5eHbKS.mjs +0 -686
  1345. package/dist/staged-Bw5eHbKS.mjs.map +0 -1
  1346. package/dist/state-BTlprvUF.mjs +0 -153
  1347. package/dist/state-BTlprvUF.mjs.map +0 -1
  1348. package/dist/status-C6IrSLMT.mjs +0 -30
  1349. package/dist/status-C6IrSLMT.mjs.map +0 -1
  1350. package/dist/storage-query-CdVF2bng.d.mts +0 -1614
  1351. package/dist/storage-query-CdVF2bng.d.mts.map +0 -1
  1352. package/dist/tar-BGOZARjy.mjs +0 -449
  1353. package/dist/tar-BGOZARjy.mjs.map +0 -1
  1354. package/dist/target-YhYH_0uc.mjs +0 -27
  1355. package/dist/target-YhYH_0uc.mjs.map +0 -1
  1356. package/dist/taxonomies-DK_XBrmT.mjs +0 -697
  1357. package/dist/taxonomies-DK_XBrmT.mjs.map +0 -1
  1358. package/dist/taxonomies-S1M3rUC0.mjs +0 -939
  1359. package/dist/taxonomies-S1M3rUC0.mjs.map +0 -1
  1360. package/dist/taxonomy-BesdHIwi.mjs +0 -525
  1361. package/dist/taxonomy-BesdHIwi.mjs.map +0 -1
  1362. package/dist/tokens-B_ffPF1q.mjs +0 -171
  1363. package/dist/tokens-B_ffPF1q.mjs.map +0 -1
  1364. package/dist/transfer-9fVSxyrV.mjs +0 -166
  1365. package/dist/transfer-9fVSxyrV.mjs.map +0 -1
  1366. package/dist/transfer-B7LUTaYD.mjs +0 -8855
  1367. package/dist/transfer-B7LUTaYD.mjs.map +0 -1
  1368. package/dist/transfer-download-BdNq4VWN.mjs +0 -33
  1369. package/dist/transfer-download-BdNq4VWN.mjs.map +0 -1
  1370. package/dist/transformations-BtQdvyly.mjs +0 -196
  1371. package/dist/transformations-BtQdvyly.mjs.map +0 -1
  1372. package/dist/transport-CriYXY1c.mjs +0 -136
  1373. package/dist/transport-CriYXY1c.mjs.map +0 -1
  1374. package/dist/transport-SnhS6xku.d.mts +0 -42
  1375. package/dist/transport-SnhS6xku.d.mts.map +0 -1
  1376. package/dist/trusted-proxy-C0eHXPd-.mjs +0 -51
  1377. package/dist/trusted-proxy-C0eHXPd-.mjs.map +0 -1
  1378. package/dist/turnstile-BNPrWd1e.mjs +0 -53
  1379. package/dist/turnstile-BNPrWd1e.mjs.map +0 -1
  1380. package/dist/types-B8sF_kE5.d.mts +0 -287
  1381. package/dist/types-B8sF_kE5.d.mts.map +0 -1
  1382. package/dist/types-BYVIYOlD.d.mts +0 -1720
  1383. package/dist/types-BYVIYOlD.d.mts.map +0 -1
  1384. package/dist/types-Bs4bi95j.d.mts +0 -374
  1385. package/dist/types-Bs4bi95j.d.mts.map +0 -1
  1386. package/dist/types-CGuODCZi.d.mts +0 -198
  1387. package/dist/types-CGuODCZi.d.mts.map +0 -1
  1388. package/dist/types-CVsmpTcU.mjs +0 -18
  1389. package/dist/types-CVsmpTcU.mjs.map +0 -1
  1390. package/dist/types-ClFEExxn.mjs +0 -37
  1391. package/dist/types-ClFEExxn.mjs.map +0 -1
  1392. package/dist/types-D994zUWB.d.mts +0 -85
  1393. package/dist/types-D994zUWB.d.mts.map +0 -1
  1394. package/dist/types-DAj1ByJQ.d.mts +0 -125
  1395. package/dist/types-DAj1ByJQ.d.mts.map +0 -1
  1396. package/dist/types-Dlv6esp4.mjs +0 -77
  1397. package/dist/types-Dlv6esp4.mjs.map +0 -1
  1398. package/dist/types-Dr1LVwL7.d.mts +0 -363
  1399. package/dist/types-Dr1LVwL7.d.mts.map +0 -1
  1400. package/dist/types-MN9bOQ1g.d.mts +0 -198
  1401. package/dist/types-MN9bOQ1g.d.mts.map +0 -1
  1402. package/dist/types-d2Szb1oz.mjs +0 -16
  1403. package/dist/types-d2Szb1oz.mjs.map +0 -1
  1404. package/dist/types-ibtP7GEA.d.mts +0 -896
  1405. package/dist/types-ibtP7GEA.d.mts.map +0 -1
  1406. package/dist/types-j6MBDjlZ.mjs +0 -103
  1407. package/dist/types-j6MBDjlZ.mjs.map +0 -1
  1408. package/dist/update-check-D8No-E5s.mjs +0 -161
  1409. package/dist/update-check-D8No-E5s.mjs.map +0 -1
  1410. package/dist/upload-attempts-BodUoFBu.mjs +0 -32
  1411. package/dist/upload-attempts-BodUoFBu.mjs.map +0 -1
  1412. package/dist/url-pattern-DGAG6ZP2.mjs +0 -41
  1413. package/dist/url-pattern-DGAG6ZP2.mjs.map +0 -1
  1414. package/dist/user-BOvuF1sQ.mjs +0 -171
  1415. package/dist/user-BOvuF1sQ.mjs.map +0 -1
  1416. package/dist/utils-Dr01wRKt.mjs +0 -439
  1417. package/dist/utils-Dr01wRKt.mjs.map +0 -1
  1418. package/dist/validate-BttYU7NK.mjs +0 -516
  1419. package/dist/validate-BttYU7NK.mjs.map +0 -1
  1420. package/dist/validate-Df09inbC.d.mts +0 -468
  1421. package/dist/validate-Df09inbC.d.mts.map +0 -1
  1422. package/dist/validation-DH2WRwWO.mjs +0 -219
  1423. package/dist/validation-DH2WRwWO.mjs.map +0 -1
  1424. package/dist/version-BbygH8CT.mjs +0 -7
  1425. package/dist/version-BbygH8CT.mjs.map +0 -1
  1426. package/dist/version-CseoDDOd.mjs +0 -9
  1427. package/dist/version-CseoDDOd.mjs.map +0 -1
  1428. package/dist/virtual-modules-82GlnQyG.mjs +0 -516
  1429. package/dist/virtual-modules-82GlnQyG.mjs.map +0 -1
  1430. package/dist/widgets-DMAgGcQt.mjs +0 -126
  1431. package/dist/widgets-DMAgGcQt.mjs.map +0 -1
  1432. package/dist/wxr-taxonomies-Bi5kFSxQ.mjs +0 -453
  1433. package/dist/wxr-taxonomies-Bi5kFSxQ.mjs.map +0 -1
  1434. package/dist/wxr-taxonomies-y_FpYlDn.d.mts +0 -34
  1435. package/dist/wxr-taxonomies-y_FpYlDn.d.mts.map +0 -1
  1436. package/dist/zod-generator-DnfG5sGQ.mjs +0 -424
  1437. package/dist/zod-generator-DnfG5sGQ.mjs.map +0 -1
  1438. /package/dist/{api-tokens-B4COohsb.mjs → api-tokens-D2zgAIEt.mjs} +0 -0
  1439. /package/dist/{ssrf-sC4_5rxm.mjs → ssrf-BvNItdxk.mjs} +0 -0
@@ -0,0 +1,3721 @@
1
+ import { t as validateIdentifier } from "./validate-DANX1-Qi.mjs";
2
+ import { t as OptionsRepository } from "./options-DhrI7eMv.mjs";
3
+ import { n as cachedQuery, t as CacheNamespace } from "./object-cache-Bo6DGaC9.mjs";
4
+ import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-Dr5Vr0zA.mjs";
5
+ import { r as requestCached } from "./request-cache-C4CIo5ad.mjs";
6
+ import { i as sanitizeHref } from "./url-Dn3IStq8.mjs";
7
+ import { t as CommentRepository } from "./comment-CWla6OHV.mjs";
8
+ import { C as toBylineInfo, t as PluginContextFactory } from "./context-CvhgHgSH.mjs";
9
+ import { a as resolveLocalizedContentRoutePath, i as resolveLocaleChain, r as resolveLocale } from "./resolve-D-kqyKns.mjs";
10
+ import { n as inspectContentPolicyDecision } from "./content-policy-Dm32n9C6.mjs";
11
+ import { t as menuTag } from "./chrome-tags-BIKBMZYO.mjs";
12
+ import { c as getDb } from "./loader-CetY659y.mjs";
13
+ import { n as videoBlockFields, r as videoNodeAttrs, t as isPortableTextVideoBlock } from "./video-DReZ48Un.mjs";
14
+ import { a as normalizePluginCapabilities, o as warnDeprecatedPluginCapabilities } from "./types-CGaqwHw4.mjs";
15
+ import { a as sanitizeGalleryImages, i as resolveImageMedia } from "./apply-Cx48saFC.mjs";
16
+ import { r as setCronTasksEnabled } from "./cron-C6ytTOLw.mjs";
17
+ import { t as PluginRouteRegistry } from "./routes-BfIxc7ft.mjs";
18
+ import { t as CommentReactionRepository } from "./comment-reaction-RZrcs5cW.mjs";
19
+ import { sql } from "kysely";
20
+ import { AsyncLocalStorage } from "node:async_hooks";
21
+ import { z } from "astro/zod";
22
+ import { PLUGIN_CAPABILITIES, isJsonPostRouteContract } from "@emdash-cms/plugin-types";
23
+ import { htmlBlockFields } from "@emdash-cms/admin/html-block";
24
+ import { UnsafePortableTextTableError, portableTextTableToProseMirror, proseMirrorTableToPortableText } from "@emdash-cms/admin/portable-text-table";
25
+ import { Extension, Mark, Node } from "@tiptap/core";
26
+
27
+ //#region src/config/errors.ts
28
+ /** A configuration failure whose message is safe to return from the API. */
29
+ var EmDashConfigurationError = class extends Error {
30
+ constructor(message, code, cause) {
31
+ super(message);
32
+ this.code = code;
33
+ this.cause = cause;
34
+ this.name = "EmDashConfigurationError";
35
+ }
36
+ };
37
+
38
+ //#endregion
39
+ //#region src/fields/image.ts
40
+ const mediaSchema = z.object({
41
+ id: z.string(),
42
+ src: z.string().optional(),
43
+ alt: z.string().optional(),
44
+ width: z.number().optional(),
45
+ height: z.number().optional(),
46
+ filename: z.string().optional(),
47
+ mimeType: z.string().optional(),
48
+ blurhash: z.string().optional(),
49
+ dominantColor: z.string().optional(),
50
+ focalX: z.number().optional(),
51
+ focalY: z.number().optional(),
52
+ provider: z.string().optional(),
53
+ previewUrl: z.string().optional(),
54
+ meta: z.record(z.string(), z.unknown()).optional()
55
+ });
56
+ const imageSchema = mediaSchema.extend({ darkVariant: mediaSchema.optional() });
57
+ function image(options = {}) {
58
+ const validation = options.allowedTypes && options.allowedTypes.length > 0 ? { allowedMimeTypes: [...options.allowedTypes] } : void 0;
59
+ return {
60
+ type: "image",
61
+ columnType: "TEXT",
62
+ schema: options.required === false ? imageSchema.optional() : imageSchema,
63
+ options,
64
+ ui: { widget: "image" },
65
+ validation
66
+ };
67
+ }
68
+
69
+ //#endregion
70
+ //#region src/fields/file.ts
71
+ function file(options = {}) {
72
+ const fileObjSchema = z.object({
73
+ id: z.string(),
74
+ url: z.string().optional(),
75
+ src: z.string().optional(),
76
+ filename: z.string().optional(),
77
+ mimeType: z.string().optional(),
78
+ size: z.number().optional(),
79
+ provider: z.string().optional(),
80
+ meta: z.record(z.string(), z.unknown()).optional()
81
+ });
82
+ return {
83
+ type: "file",
84
+ columnType: "TEXT",
85
+ schema: options.required ? fileObjSchema : fileObjSchema.optional(),
86
+ options,
87
+ ui: {
88
+ widget: "file",
89
+ helpText: options.helpText,
90
+ maxSize: options.maxSize
91
+ },
92
+ validation: options.allowedTypes && options.allowedTypes.length > 0 ? { allowedMimeTypes: [...options.allowedTypes] } : void 0
93
+ };
94
+ }
95
+
96
+ //#endregion
97
+ //#region src/fields/reference.ts
98
+ /**
99
+ * Reference field
100
+ * References another content item by ID
101
+ */
102
+ function reference(collection, options) {
103
+ const schema = z.string();
104
+ return {
105
+ type: "reference",
106
+ columnType: "TEXT",
107
+ schema: options?.required === false ? schema.optional() : schema,
108
+ options: {
109
+ ...options,
110
+ collection
111
+ },
112
+ ui: { widget: "reference" }
113
+ };
114
+ }
115
+
116
+ //#endregion
117
+ //#region src/fields/portable-text.ts
118
+ /**
119
+ * Portable Text block schema
120
+ */
121
+ const portableTextBlockSchema = z.object({
122
+ _type: z.string(),
123
+ _key: z.string()
124
+ }).loose();
125
+ /**
126
+ * Portable Text field
127
+ * Stores structured content in Portable Text format
128
+ */
129
+ function portableText(options) {
130
+ const schema = z.array(portableTextBlockSchema);
131
+ return {
132
+ type: "portableText",
133
+ columnType: "JSON",
134
+ schema: options?.required === false ? schema.optional() : schema,
135
+ options,
136
+ ui: { widget: "portableText" }
137
+ };
138
+ }
139
+
140
+ //#endregion
141
+ //#region src/content/converters/mark-safety.ts
142
+ const SUPPORTED_PORTABLE_TEXT_DECORATORS = new Set([
143
+ "strong",
144
+ "em",
145
+ "underline",
146
+ "strike-through",
147
+ "subscript",
148
+ "superscript",
149
+ "code"
150
+ ]);
151
+ const SUPPORTED_PROSEMIRROR_MARKS = new Set([
152
+ "bold",
153
+ "strong",
154
+ "italic",
155
+ "em",
156
+ "underline",
157
+ "strike",
158
+ "strikethrough",
159
+ "subscript",
160
+ "superscript",
161
+ "code",
162
+ "link",
163
+ "emdashPortableTextSpan"
164
+ ]);
165
+ function isRecord$2(value) {
166
+ return typeof value === "object" && value !== null && !Array.isArray(value);
167
+ }
168
+ function collectPortableTextMarks(value, inheritedMarkDefs, unsupported) {
169
+ if (Array.isArray(value)) {
170
+ for (const item of value) collectPortableTextMarks(item, inheritedMarkDefs, unsupported);
171
+ return;
172
+ }
173
+ if (!isRecord$2(value)) return;
174
+ let markDefs = inheritedMarkDefs;
175
+ if (Array.isArray(value.markDefs)) {
176
+ const localMarkDefs = new Map(inheritedMarkDefs);
177
+ for (const markDef of value.markDefs) if (isRecord$2(markDef) && typeof markDef._key === "string") localMarkDefs.set(markDef._key, markDef);
178
+ markDefs = localMarkDefs;
179
+ }
180
+ if (value._type === "span" && Array.isArray(value.marks)) for (const mark of value.marks) {
181
+ if (typeof mark !== "string" || SUPPORTED_PORTABLE_TEXT_DECORATORS.has(mark)) continue;
182
+ const markDef = markDefs.get(mark);
183
+ if (markDef?._type === "link") continue;
184
+ unsupported.add(typeof markDef?._type === "string" ? markDef._type : mark);
185
+ }
186
+ for (const [key, child] of Object.entries(value)) if (key !== "markDefs") collectPortableTextMarks(child, markDefs, unsupported);
187
+ }
188
+ function collectProseMirrorMarks(node, unsupported) {
189
+ for (const mark of node.marks ?? []) if (!SUPPORTED_PROSEMIRROR_MARKS.has(mark.type)) unsupported.add(mark.type);
190
+ for (const child of node.content ?? []) collectProseMirrorMarks(child, unsupported);
191
+ }
192
+ var UnsupportedPortableTextMarksError = class extends Error {
193
+ marks;
194
+ constructor(marks) {
195
+ const sortedMarks = [...new Set(marks)].toSorted();
196
+ super(`Unsupported Portable Text marks: ${sortedMarks.join(", ")}`);
197
+ this.name = "UnsupportedPortableTextMarksError";
198
+ this.marks = sortedMarks;
199
+ }
200
+ };
201
+ function findUnsupportedPortableTextMarks(blocks) {
202
+ const unsupported = /* @__PURE__ */ new Set();
203
+ for (const block of blocks) if (block._type === "block" || block._type === "table") collectPortableTextMarks(block, /* @__PURE__ */ new Map(), unsupported);
204
+ return [...unsupported].toSorted();
205
+ }
206
+ function assertPortableTextMarksSupported(blocks) {
207
+ const unsupported = findUnsupportedPortableTextMarks(blocks);
208
+ if (unsupported.length > 0) throw new UnsupportedPortableTextMarksError(unsupported);
209
+ }
210
+ function assertProseMirrorMarksSupported(document) {
211
+ const unsupported = /* @__PURE__ */ new Set();
212
+ for (const node of document.content) collectProseMirrorMarks(node, unsupported);
213
+ if (unsupported.size > 0) throw new UnsupportedPortableTextMarksError(unsupported);
214
+ }
215
+
216
+ //#endregion
217
+ //#region src/content/converters/numbered-list.ts
218
+ const MAX_ORDERED_LIST_START = 2147483647;
219
+ function normalizeListId(value) {
220
+ if (typeof value !== "string") return void 0;
221
+ const normalized = value.trim();
222
+ return normalized.length > 0 && normalized.length <= 128 ? normalized : void 0;
223
+ }
224
+ function normalizeListStart(value) {
225
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_ORDERED_LIST_START ? value : void 0;
226
+ }
227
+ function deriveLegacyListId(seed) {
228
+ const readable = `legacy:${seed}`;
229
+ if (readable.length <= 128) return readable;
230
+ let hash = 2166136261;
231
+ for (let i = 0; i < seed.length; i++) {
232
+ hash ^= seed.charCodeAt(i);
233
+ hash = Math.imul(hash, 16777619);
234
+ }
235
+ return `legacy:${seed.slice(0, 96)}:${(hash >>> 0).toString(36)}:${seed.length.toString(36)}`;
236
+ }
237
+ function readOrderedListMetadata(attrs, fallbackId) {
238
+ return {
239
+ listId: normalizeListId(attrs?.listId) ?? deriveLegacyListId(fallbackId),
240
+ listStart: normalizeListStart(attrs?.listStart) ?? normalizeListStart(attrs?.start) ?? 1
241
+ };
242
+ }
243
+ function collectProseMirrorOrderedLists(doc) {
244
+ const lists = [];
245
+ const visit = (node, path, depth, context) => {
246
+ if (node.type === "orderedList") lists.push({
247
+ node,
248
+ path,
249
+ depth,
250
+ context
251
+ });
252
+ for (const [index, child] of (node.content ?? []).entries()) {
253
+ const childPath = `${path}:${index}`;
254
+ visit(child, childPath, depth + 1, child.type === "listItem" ? `listItem:${childPath}` : context);
255
+ }
256
+ };
257
+ for (const [index, node] of doc.content.entries()) visit(node, `root:${index}`, 0, "root");
258
+ return lists;
259
+ }
260
+ function cloneProseMirrorNode(node) {
261
+ return {
262
+ ...node,
263
+ attrs: node.attrs ? { ...node.attrs } : void 0,
264
+ content: node.content?.map(cloneProseMirrorNode),
265
+ marks: node.marks?.map((mark) => ({
266
+ ...mark,
267
+ attrs: mark.attrs ? { ...mark.attrs } : void 0
268
+ }))
269
+ };
270
+ }
271
+ function createRepairId(sourceId, scope, attempt) {
272
+ const base = deriveLegacyListId(`repair:${sourceId}:${scope}`);
273
+ const suffix = `:${attempt.toString(36)}`;
274
+ return `${base.slice(0, 128 - suffix.length)}${suffix}`;
275
+ }
276
+ function normalizeProseMirrorOrderedListJson(doc) {
277
+ const normalized = {
278
+ ...doc,
279
+ content: doc.content.map(cloneProseMirrorNode)
280
+ };
281
+ const canonicalBySourceScope = /* @__PURE__ */ new Map();
282
+ const assignedCanonicalIds = /* @__PURE__ */ new Set();
283
+ const lists = collectProseMirrorOrderedLists(normalized).map((list) => {
284
+ const sourceId = normalizeListId(list.node.attrs?.listId) ?? deriveLegacyListId(`pm-json:${list.path}:${list.depth}:${list.context}`);
285
+ const scope = JSON.stringify([list.depth, list.context]);
286
+ const sourceScope = JSON.stringify([sourceId, scope]);
287
+ let listId = canonicalBySourceScope.get(sourceScope);
288
+ if (!listId) {
289
+ if (!assignedCanonicalIds.has(sourceId)) listId = sourceId;
290
+ else {
291
+ let attempt = 0;
292
+ do
293
+ listId = createRepairId(sourceId, scope, attempt++);
294
+ while (assignedCanonicalIds.has(listId));
295
+ }
296
+ canonicalBySourceScope.set(sourceScope, listId);
297
+ assignedCanonicalIds.add(listId);
298
+ }
299
+ return {
300
+ ...list,
301
+ listId,
302
+ scopeKey: JSON.stringify([
303
+ listId,
304
+ list.depth,
305
+ list.context
306
+ ])
307
+ };
308
+ });
309
+ const bases = /* @__PURE__ */ new Map();
310
+ for (const list of lists) {
311
+ const listStart = normalizeListStart(list.node.attrs?.listStart);
312
+ if (listStart !== void 0 && !bases.has(list.scopeKey)) bases.set(list.scopeKey, listStart);
313
+ }
314
+ for (const list of lists) if (!bases.has(list.scopeKey)) bases.set(list.scopeKey, normalizeListStart(list.node.attrs?.start) ?? 1);
315
+ const counts = /* @__PURE__ */ new Map();
316
+ for (const list of lists) {
317
+ const listStart = bases.get(list.scopeKey);
318
+ const count = counts.get(list.scopeKey) ?? 0;
319
+ const start = normalizeListStart(listStart + count) ?? 1;
320
+ const directItemCount = list.node.content?.filter((node) => node.type === "listItem").length ?? 0;
321
+ counts.set(list.scopeKey, count + directItemCount);
322
+ list.node.attrs = {
323
+ ...list.node.attrs,
324
+ listId: list.listId,
325
+ listStart,
326
+ start
327
+ };
328
+ }
329
+ return normalized;
330
+ }
331
+
332
+ //#endregion
333
+ //#region src/content/converters/portable-text-identity.ts
334
+ const PORTABLE_TEXT_BLOCK_NODE = "emdashPortableTextBlock";
335
+ const PORTABLE_TEXT_BLOCK_ATTR = "emdashPortableTextBlock";
336
+ const PORTABLE_TEXT_KEY_ATTR = "emdashPortableTextKey";
337
+ const PORTABLE_TEXT_SPAN_MARK = "emdashPortableTextSpan";
338
+ function isRecord$1(value) {
339
+ return typeof value === "object" && value !== null && !Array.isArray(value);
340
+ }
341
+ function attrsWithPortableTextKey(attrs, key) {
342
+ return {
343
+ ...attrs,
344
+ [PORTABLE_TEXT_KEY_ATTR]: key
345
+ };
346
+ }
347
+ function portableTextKeyFromAttrs(attrs) {
348
+ const key = attrs?.[PORTABLE_TEXT_KEY_ATTR];
349
+ return typeof key === "string" && key ? key : void 0;
350
+ }
351
+ function portableTextSpanKeyFromMarks(marks) {
352
+ for (const mark of marks ?? []) {
353
+ if (!isRecord$1(mark) || mark.type !== PORTABLE_TEXT_SPAN_MARK || !isRecord$1(mark.attrs)) continue;
354
+ const key = mark.attrs.key;
355
+ if (typeof key === "string" && key) return key;
356
+ }
357
+ }
358
+ function portableTextMarkDefsFromMarks(marks) {
359
+ const identity = (marks ?? []).find((mark) => isRecord$1(mark) && mark.type === PORTABLE_TEXT_SPAN_MARK && isRecord$1(mark.attrs));
360
+ if (!isRecord$1(identity) || !isRecord$1(identity.attrs) || !Array.isArray(identity.attrs.markDefs)) return [];
361
+ return identity.attrs.markDefs.filter((markDef) => isRecord$1(markDef) && typeof markDef._type === "string" && typeof markDef._key === "string");
362
+ }
363
+ function portableTextBlockFromAttrs(attrs) {
364
+ const block = attrs?.[PORTABLE_TEXT_BLOCK_ATTR];
365
+ if (!isRecord$1(block) || typeof block._type !== "string" || typeof block._key !== "string") return;
366
+ return {
367
+ ...block,
368
+ _type: block._type,
369
+ _key: block._key
370
+ };
371
+ }
372
+ const PortableTextIdentityExtension = Extension.create({
373
+ name: "emdashPortableTextIdentity",
374
+ addGlobalAttributes() {
375
+ const hiddenAttribute = {
376
+ default: null,
377
+ rendered: false
378
+ };
379
+ return [{
380
+ types: [
381
+ "paragraph",
382
+ "heading",
383
+ "blockquote",
384
+ "codeBlock",
385
+ "htmlBlock",
386
+ "iframeBlock",
387
+ "image",
388
+ "videoBlock",
389
+ "horizontalRule",
390
+ "gallery",
391
+ PORTABLE_TEXT_BLOCK_NODE
392
+ ],
393
+ attributes: { [PORTABLE_TEXT_KEY_ATTR]: hiddenAttribute }
394
+ }];
395
+ }
396
+ });
397
+ const PortableTextSpanIdentity = Mark.create({
398
+ name: PORTABLE_TEXT_SPAN_MARK,
399
+ inclusive: false,
400
+ spanning: false,
401
+ addAttributes() {
402
+ return {
403
+ key: {
404
+ default: null,
405
+ rendered: false
406
+ },
407
+ markDefs: {
408
+ default: [],
409
+ rendered: false
410
+ }
411
+ };
412
+ },
413
+ parseHTML() {
414
+ return [];
415
+ },
416
+ renderHTML() {
417
+ return ["span", 0];
418
+ }
419
+ });
420
+ const PortableTextOpaqueBlock = Node.create({
421
+ name: PORTABLE_TEXT_BLOCK_NODE,
422
+ group: "block",
423
+ atom: true,
424
+ selectable: true,
425
+ draggable: true,
426
+ addAttributes() {
427
+ return { [PORTABLE_TEXT_BLOCK_ATTR]: {
428
+ default: null,
429
+ rendered: false
430
+ } };
431
+ },
432
+ parseHTML() {
433
+ return [{ tag: "div[data-emdash-portable-text-block]" }];
434
+ },
435
+ renderHTML({ node }) {
436
+ const blockType = portableTextBlockFromAttrs(node.attrs)?._type ?? "unknown";
437
+ return [
438
+ "div",
439
+ {
440
+ "data-emdash-portable-text-block": blockType,
441
+ contenteditable: "false"
442
+ },
443
+ `[Unknown block type: ${blockType}]`
444
+ ];
445
+ }
446
+ });
447
+ const portableTextIdentityExtensions = [
448
+ PortableTextIdentityExtension,
449
+ PortableTextSpanIdentity,
450
+ PortableTextOpaqueBlock
451
+ ];
452
+
453
+ //#endregion
454
+ //#region src/content/converters/prosemirror-to-portable-text.ts
455
+ /**
456
+ * ProseMirror to Portable Text Converter
457
+ *
458
+ * Converts TipTap's ProseMirror JSON format to Portable Text for storage.
459
+ */
460
+ /**
461
+ * Generate a unique key for Portable Text blocks
462
+ */
463
+ function generateKey$1() {
464
+ return Math.random().toString(36).substring(2, 11);
465
+ }
466
+ /**
467
+ * Convert ProseMirror document to Portable Text
468
+ */
469
+ function prosemirrorToPortableText(doc) {
470
+ if (!doc || doc.type !== "doc" || !doc.content) return [];
471
+ assertProseMirrorMarksSupported(doc);
472
+ const blocks = [];
473
+ const usedBlockKeys = /* @__PURE__ */ new Set();
474
+ for (const [i, node] of doc.content.entries()) {
475
+ if (i === doc.content.length - 1 && isUnkeyedEmptyParagraph(node)) continue;
476
+ const converted = convertNode(node, `root:${i}`);
477
+ for (const block of converted ? Array.isArray(converted) ? converted : [converted] : []) {
478
+ let key = block._key;
479
+ if (usedBlockKeys.has(key)) do
480
+ key = generateKey$1();
481
+ while (usedBlockKeys.has(key));
482
+ usedBlockKeys.add(key);
483
+ blocks.push(key === block._key ? block : {
484
+ ...block,
485
+ _key: key
486
+ });
487
+ }
488
+ }
489
+ return blocks;
490
+ }
491
+ function isUnkeyedEmptyParagraph(node) {
492
+ return node.type === "paragraph" && (node.content?.length ?? 0) === 0 && portableTextKeyFromAttrs(node.attrs) === void 0;
493
+ }
494
+ /**
495
+ * Convert a single ProseMirror node to Portable Text block(s)
496
+ */
497
+ function convertNode(node, path) {
498
+ switch (node.type) {
499
+ case PORTABLE_TEXT_BLOCK_NODE: return portableTextBlockFromAttrs(node.attrs) ?? null;
500
+ case "paragraph": return convertParagraph(node);
501
+ case "heading": return convertHeading(node);
502
+ case "bulletList": return convertList$1(node, "bullet", path);
503
+ case "orderedList": return convertList$1(node, "number", path);
504
+ case "blockquote": return convertBlockquote(node);
505
+ case "codeBlock": return convertCodeBlock$1(node);
506
+ case "htmlBlock": return convertHtmlBlock(node);
507
+ case "iframeBlock": return convertIframeBlock(node);
508
+ case "image": return convertImage$1(node);
509
+ case "videoBlock": return convertVideo(node);
510
+ case "gallery": return convertGallery(node);
511
+ case "table": {
512
+ const result = proseMirrorTableToPortableText(node, {
513
+ path,
514
+ createKey: generateKey$1,
515
+ inlineToSpans: (content) => {
516
+ const { children, markDefs } = convertInlineContent(content, true);
517
+ return {
518
+ content: children,
519
+ markDefs
520
+ };
521
+ }
522
+ });
523
+ if (!result.ok) throw new UnsafePortableTextTableError(result.reason, result.raw, result.renderFallback);
524
+ return result.table;
525
+ }
526
+ case "horizontalRule": return {
527
+ _type: "break",
528
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
529
+ style: "lineBreak"
530
+ };
531
+ default: return {
532
+ _type: node.type,
533
+ _key: generateKey$1(),
534
+ ...node.attrs,
535
+ _pmContent: node.content
536
+ };
537
+ }
538
+ }
539
+ /**
540
+ * Convert paragraph to Portable Text block
541
+ */
542
+ function convertParagraph(node) {
543
+ const { children, markDefs } = convertInlineContent(node.content || []);
544
+ if (children.length === 0) return null;
545
+ const ta = node.attrs?.textAlign;
546
+ const textAlign = ta === "center" || ta === "right" || ta === "justify" ? ta : void 0;
547
+ return {
548
+ _type: "block",
549
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
550
+ style: "normal",
551
+ children,
552
+ ...markDefs.length > 0 ? { markDefs } : {},
553
+ ...textAlign ? { textAlign } : {}
554
+ };
555
+ }
556
+ /** Map heading level number to Portable Text style */
557
+ function headingLevelToStyle(level) {
558
+ switch (level) {
559
+ case 1: return "h1";
560
+ case 2: return "h2";
561
+ case 3: return "h3";
562
+ case 4: return "h4";
563
+ case 5: return "h5";
564
+ case 6: return "h6";
565
+ default: return "h1";
566
+ }
567
+ }
568
+ /**
569
+ * Convert heading to Portable Text block
570
+ */
571
+ function convertHeading(node) {
572
+ const { children, markDefs } = convertInlineContent(node.content || []);
573
+ const style = headingLevelToStyle(typeof node.attrs?.level === "number" ? node.attrs.level : 1);
574
+ if (children.length === 0) return null;
575
+ const ta = node.attrs?.textAlign;
576
+ const textAlign = ta === "center" || ta === "right" || ta === "justify" ? ta : void 0;
577
+ return {
578
+ _type: "block",
579
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
580
+ style,
581
+ children,
582
+ markDefs: markDefs.length > 0 ? markDefs : void 0,
583
+ ...textAlign ? { textAlign } : {}
584
+ };
585
+ }
586
+ /**
587
+ * Convert list to Portable Text blocks
588
+ */
589
+ function convertList$1(node, listItem, path) {
590
+ const blocks = [];
591
+ const metadata = listItem === "number" ? readOrderedListMetadata(node.attrs, path) : void 0;
592
+ for (const [i, item] of (node.content ?? []).entries()) if (item.type === "listItem") {
593
+ const itemBlocks = convertListItem$1(item, listItem, 1, `${path}:${i}`, metadata);
594
+ blocks.push(...itemBlocks);
595
+ }
596
+ return blocks;
597
+ }
598
+ /**
599
+ * Convert list item to Portable Text blocks
600
+ */
601
+ function convertListItem$1(item, listItem, level, path, metadata) {
602
+ const blocks = [];
603
+ for (const [i, child] of (item.content ?? []).entries()) if (child.type === "paragraph") {
604
+ const { children, markDefs } = convertInlineContent(child.content || []);
605
+ if (children.length > 0) blocks.push({
606
+ _type: "block",
607
+ _key: portableTextKeyFromAttrs(child.attrs) ?? generateKey$1(),
608
+ style: "normal",
609
+ listItem,
610
+ level,
611
+ ...metadata,
612
+ children,
613
+ markDefs: markDefs.length > 0 ? markDefs : void 0
614
+ });
615
+ } else if (child.type === "bulletList") blocks.push(...convertListItemNested(child, "bullet", level + 1, `${path}:${i}`));
616
+ else if (child.type === "orderedList") blocks.push(...convertListItemNested(child, "number", level + 1, `${path}:${i}`));
617
+ return blocks;
618
+ }
619
+ /**
620
+ * Convert nested list
621
+ */
622
+ function convertListItemNested(node, listItem, level, path) {
623
+ const blocks = [];
624
+ const metadata = listItem === "number" ? readOrderedListMetadata(node.attrs, path) : void 0;
625
+ for (const [i, item] of (node.content ?? []).entries()) if (item.type === "listItem") blocks.push(...convertListItem$1(item, listItem, level, `${path}:${i}`, metadata));
626
+ return blocks;
627
+ }
628
+ /**
629
+ * Convert blockquote to Portable Text blocks
630
+ */
631
+ function convertBlockquote(node) {
632
+ const blocks = [];
633
+ for (const child of node.content || []) if (child.type === "paragraph") {
634
+ const { children, markDefs } = convertInlineContent(child.content || []);
635
+ if (children.length > 0) blocks.push({
636
+ _type: "block",
637
+ _key: portableTextKeyFromAttrs(child.attrs) ?? portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
638
+ style: "blockquote",
639
+ children,
640
+ markDefs: markDefs.length > 0 ? markDefs : void 0
641
+ });
642
+ }
643
+ return blocks.length === 1 ? blocks[0] : blocks.length > 0 ? blocks : null;
644
+ }
645
+ /**
646
+ * Convert code block to Portable Text
647
+ */
648
+ function convertCodeBlock$1(node) {
649
+ const code = node.content?.map((n) => n.text || "").join("") || "";
650
+ const language = typeof node.attrs?.language === "string" ? node.attrs.language : void 0;
651
+ return {
652
+ _type: "code",
653
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
654
+ code,
655
+ language: language || void 0
656
+ };
657
+ }
658
+ /**
659
+ * Convert HTML block to Portable Text
660
+ */
661
+ function convertHtmlBlock(node) {
662
+ return {
663
+ _type: "htmlBlock",
664
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
665
+ ...htmlBlockFields(node.attrs ?? {})
666
+ };
667
+ }
668
+ /**
669
+ * Convert iframe block to Portable Text, writing optional fields only when set
670
+ */
671
+ function convertIframeBlock(node) {
672
+ const { src, title, width, height, allow, allowFullscreen } = node.attrs ?? {};
673
+ const block = {
674
+ _type: "iframe",
675
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
676
+ src: typeof src === "string" ? src : ""
677
+ };
678
+ if (typeof title === "string" && title) block.title = title;
679
+ if (isFrameDimension$1(width)) block.width = width;
680
+ if (isFrameDimension$1(height)) block.height = height;
681
+ if (typeof allow === "string" && allow) block.allow = allow;
682
+ if (allowFullscreen === true) block.allowFullscreen = true;
683
+ return block;
684
+ }
685
+ /**
686
+ * Convert video block to Portable Text
687
+ */
688
+ function convertVideo(node) {
689
+ return {
690
+ _type: "video",
691
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
692
+ ...videoBlockFields(node.attrs ?? {})
693
+ };
694
+ }
695
+ function isFrameDimension$1(value) {
696
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 1e4;
697
+ }
698
+ function imageDimension$1(value) {
699
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
700
+ }
701
+ /**
702
+ * Convert image to Portable Text
703
+ */
704
+ function convertImage$1(node) {
705
+ const attrs = node.attrs;
706
+ const provider = typeof attrs?.provider === "string" ? attrs.provider : void 0;
707
+ const mediaId = typeof attrs?.mediaId === "string" ? attrs.mediaId : void 0;
708
+ const src = typeof attrs?.src === "string" ? attrs.src : "";
709
+ const alt = typeof attrs?.alt === "string" ? attrs.alt : void 0;
710
+ const title = typeof attrs?.title === "string" ? attrs.title : void 0;
711
+ const caption = attrs && Object.hasOwn(attrs, "caption") ? typeof attrs.caption === "string" ? attrs.caption : void 0 : title;
712
+ const width = imageDimension$1(attrs?.width);
713
+ const height = imageDimension$1(attrs?.height);
714
+ const displayWidth = imageDimension$1(attrs?.displayWidth);
715
+ const displayHeight = imageDimension$1(attrs?.displayHeight);
716
+ const alignment = attrs?.alignment;
717
+ let link;
718
+ const rawLink = attrs?.link;
719
+ if (rawLink && typeof rawLink === "object") {
720
+ const linkObj = rawLink;
721
+ const href = typeof linkObj.href === "string" ? linkObj.href.trim() : "";
722
+ if (href) {
723
+ link = { href };
724
+ if (linkObj.blank === true) link.blank = true;
725
+ }
726
+ }
727
+ return {
728
+ _type: "image",
729
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
730
+ asset: {
731
+ _ref: mediaId || src || "",
732
+ url: src || "",
733
+ provider: provider && provider !== "local" ? provider : void 0
734
+ },
735
+ alt: alt || void 0,
736
+ caption: caption || (title ? "" : void 0),
737
+ title: title || void 0,
738
+ width: width || void 0,
739
+ height: height || void 0,
740
+ displayWidth: displayWidth || void 0,
741
+ displayHeight: displayHeight || void 0,
742
+ alignment: alignment === "left" || alignment === "center" || alignment === "right" || alignment === "wide" || alignment === "full" ? alignment : void 0,
743
+ link
744
+ };
745
+ }
746
+ /**
747
+ * Convert gallery node to Portable Text
748
+ */
749
+ function convertGallery(node) {
750
+ const columns = node.attrs?.columns;
751
+ return {
752
+ _type: "gallery",
753
+ _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey$1(),
754
+ images: sanitizeGalleryImages(node.attrs?.images, generateKey$1),
755
+ ...typeof columns === "number" ? { columns } : {}
756
+ };
757
+ }
758
+ /**
759
+ * Convert inline content (text nodes with marks) to Portable Text spans
760
+ */
761
+ function convertInlineContent(nodes, preserveHardBreakBoundary = false) {
762
+ const children = [];
763
+ const markDefs = [];
764
+ const markDefMap = /* @__PURE__ */ new Map();
765
+ const usedSpanKeys = /* @__PURE__ */ new Set();
766
+ const claimSpanKey = (preferred) => {
767
+ if (preferred && !usedSpanKeys.has(preferred)) {
768
+ usedSpanKeys.add(preferred);
769
+ return preferred;
770
+ }
771
+ let key;
772
+ do
773
+ key = generateKey$1();
774
+ while (usedSpanKeys.has(key));
775
+ usedSpanKeys.add(key);
776
+ return key;
777
+ };
778
+ for (const node of nodes) if (node.type === "text" && node.text) {
779
+ const marks = [];
780
+ const originalMarkDefs = portableTextMarkDefsFromMarks(node.marks);
781
+ for (const mark of node.marks || []) {
782
+ const markType = convertMark(mark, markDefs, markDefMap, originalMarkDefs);
783
+ if (markType) marks.push(markType);
784
+ }
785
+ const preferredKey = portableTextSpanKeyFromMarks(node.marks) ?? portableTextKeyFromAttrs(node.attrs);
786
+ const normalizedMarks = marks.length > 0 ? marks : void 0;
787
+ const previous = children.at(-1);
788
+ if (preferredKey && previous?._key === preferredKey && JSON.stringify(previous.marks) === JSON.stringify(normalizedMarks)) {
789
+ previous.text += node.text;
790
+ continue;
791
+ }
792
+ children.push({
793
+ _type: "span",
794
+ _key: claimSpanKey(preferredKey),
795
+ text: node.text,
796
+ marks: normalizedMarks
797
+ });
798
+ } else if (node.type === "hardBreak") if (children.length > 0 && !preserveHardBreakBoundary) {
799
+ const lastChild = children.at(-1);
800
+ lastChild.text += "\n";
801
+ } else children.push({
802
+ _type: "span",
803
+ _key: claimSpanKey(portableTextSpanKeyFromMarks(node.marks) ?? portableTextKeyFromAttrs(node.attrs)),
804
+ text: "\n"
805
+ });
806
+ if (children.length === 0) children.push({
807
+ _type: "span",
808
+ _key: claimSpanKey(),
809
+ text: ""
810
+ });
811
+ return {
812
+ children,
813
+ markDefs
814
+ };
815
+ }
816
+ /**
817
+ * Convert a ProseMirror mark to Portable Text mark
818
+ */
819
+ function convertMark(mark, markDefs, markDefMap, originalMarkDefs) {
820
+ switch (mark.type) {
821
+ case "bold":
822
+ case "strong": return "strong";
823
+ case "italic":
824
+ case "em": return "em";
825
+ case "underline": return "underline";
826
+ case "strike":
827
+ case "strikethrough": return "strike-through";
828
+ case "subscript": return "subscript";
829
+ case "superscript": return "superscript";
830
+ case "code": return "code";
831
+ case PORTABLE_TEXT_SPAN_MARK: return null;
832
+ case "link": {
833
+ const href = (typeof mark.attrs?.href === "string" ? mark.attrs.href : "") || "";
834
+ const blank = mark.attrs?.target === "_blank";
835
+ const originalMarkDef = originalMarkDefs.find((markDef) => markDef._type === "link");
836
+ const mapKey = originalMarkDef ? `key:${originalMarkDef._key}` : `value:${JSON.stringify([href, blank])}`;
837
+ if (markDefMap.has(mapKey)) return markDefMap.get(mapKey);
838
+ const key = originalMarkDef?._key || generateKey$1();
839
+ markDefs.push({
840
+ ...originalMarkDef,
841
+ _type: "link",
842
+ _key: key,
843
+ href,
844
+ ...originalMarkDef ? blank || Object.hasOwn(originalMarkDef, "blank") ? { blank } : {} : { blank }
845
+ });
846
+ markDefMap.set(mapKey, key);
847
+ return key;
848
+ }
849
+ default: throw new UnsupportedPortableTextMarksError([mark.type]);
850
+ }
851
+ }
852
+
853
+ //#endregion
854
+ //#region src/content/converters/image-link.ts
855
+ function normalizeImageLink(raw) {
856
+ if (typeof raw === "string") {
857
+ const href = raw.trim();
858
+ return href ? { href } : null;
859
+ }
860
+ if (typeof raw !== "object" || raw === null) return null;
861
+ const { href, blank } = raw;
862
+ if (typeof href !== "string") return null;
863
+ const trimmed = href.trim();
864
+ if (!trimmed) return null;
865
+ return blank === true ? {
866
+ href: trimmed,
867
+ blank: true
868
+ } : { href: trimmed };
869
+ }
870
+
871
+ //#endregion
872
+ //#region src/content/converters/portable-text-to-prosemirror.ts
873
+ /**
874
+ * Portable Text to ProseMirror Converter
875
+ *
876
+ * Converts Portable Text to TipTap's ProseMirror JSON format for editing.
877
+ */
878
+ function generateKey() {
879
+ return Math.random().toString(36).substring(2, 11);
880
+ }
881
+ /** Convert Portable Text to a ProseMirror document. */
882
+ function portableTextToProsemirror(blocks, options = {}) {
883
+ if (!blocks || blocks.length === 0) return {
884
+ type: "doc",
885
+ content: [{ type: "paragraph" }]
886
+ };
887
+ assertPortableTextMarksSupported(blocks);
888
+ const preserveIdentity = options.preserveIdentity === true;
889
+ const content = [];
890
+ let i = 0;
891
+ while (i < blocks.length) {
892
+ const block = blocks[i];
893
+ if (isTextBlock(block) && block.listItem) {
894
+ const listBlocks = [];
895
+ const listType = block.listItem;
896
+ const runStart = i;
897
+ const rootId = listType === "number" ? normalizeListId(block.listId) : void 0;
898
+ while (i < blocks.length) {
899
+ const current = blocks[i];
900
+ if (!isTextBlock(current) || !current.listItem) break;
901
+ const level = current.level || 1;
902
+ const currentId = current.listItem === "number" ? normalizeListId(current.listId) : void 0;
903
+ const sameRootIdentity = listType !== "number" || level > 1 || (rootId ? currentId === rootId : currentId === void 0);
904
+ if (level > 1 || current.listItem === listType && sameRootIdentity) {
905
+ listBlocks.push(current);
906
+ i++;
907
+ } else break;
908
+ }
909
+ content.push(convertList(listBlocks, listType, `root:${runStart}`, preserveIdentity));
910
+ } else if (isTextBlock(block) && block.style === "blockquote") {
911
+ const quoteBlocks = [];
912
+ while (i < blocks.length) {
913
+ const current = blocks[i];
914
+ if (!isTextBlock(current) || current.style !== "blockquote" || current.listItem !== void 0) break;
915
+ quoteBlocks.push(current);
916
+ i++;
917
+ }
918
+ content.push({
919
+ type: "blockquote",
920
+ content: quoteBlocks.map((quoteBlock) => {
921
+ const paragraph = convertSpans(quoteBlock.children, quoteBlock.markDefs || [], preserveIdentity);
922
+ return {
923
+ type: "paragraph",
924
+ attrs: identityAttrs(void 0, quoteBlock._key, preserveIdentity),
925
+ content: paragraph.length > 0 ? paragraph : void 0
926
+ };
927
+ })
928
+ });
929
+ } else {
930
+ const converted = convertBlock(block, `root:${i}`, preserveIdentity);
931
+ if (converted) content.push(converted);
932
+ i++;
933
+ }
934
+ }
935
+ return normalizeProseMirrorOrderedListJson({
936
+ type: "doc",
937
+ content: content.length > 0 ? content : [{ type: "paragraph" }]
938
+ });
939
+ }
940
+ function identityAttrs(attrs, key, preserveIdentity) {
941
+ return preserveIdentity ? attrsWithPortableTextKey(attrs, key) : attrs;
942
+ }
943
+ function getListMetadata(item, fallbackSeed) {
944
+ const listId = normalizeListId(item.listId) ?? deriveLegacyListId(fallbackSeed);
945
+ const listStart = normalizeListStart(item.listStart);
946
+ return {
947
+ listId,
948
+ ...listStart === void 0 ? {} : { listStart }
949
+ };
950
+ }
951
+ function belongsToNestedGroup(item, minLevel, parentListType, anchorType, anchorId) {
952
+ if ((item.level || 2) > minLevel) return true;
953
+ if ((item.listItem || parentListType) !== anchorType) return false;
954
+ if (anchorType !== "number") return true;
955
+ const itemId = normalizeListId(item.listId);
956
+ return anchorId ? itemId === anchorId : itemId === void 0;
957
+ }
958
+ /**
959
+ * Type guard for text blocks
960
+ */
961
+ function isTextBlock(block) {
962
+ return block._type === "block";
963
+ }
964
+ /**
965
+ * Type guard for image blocks.
966
+ * Checks both `_type` and that `asset` is a valid object — image blocks
967
+ * without an `asset` wrapper (e.g. `{ _type: "image", url: "..." }`) are
968
+ * malformed and should not be cast to `PortableTextImageBlock`.
969
+ */
970
+ function isImageBlock(block) {
971
+ return block._type === "image" && "asset" in block && typeof block.asset === "object" && block.asset !== null;
972
+ }
973
+ /**
974
+ * Type guard for gallery blocks that carry a usable `images` array.
975
+ * Galleries without one are handled separately in `convertBlock`.
976
+ */
977
+ function isGalleryBlock(block) {
978
+ return block._type === "gallery" && "images" in block && Array.isArray(block.images);
979
+ }
980
+ const isString = (value) => typeof value === "string";
981
+ const isFrameDimension = (value) => typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 1e4;
982
+ const IFRAME_BLOCK_FIELDS = new Map([
983
+ ["_type", () => true],
984
+ ["_key", () => true],
985
+ ["src", isString],
986
+ ["title", isString],
987
+ ["width", isFrameDimension],
988
+ ["height", isFrameDimension],
989
+ ["allow", isString],
990
+ ["allowFullscreen", (value) => typeof value === "boolean"]
991
+ ]);
992
+ /**
993
+ * An `iframe` block with other fields, or with values of other types, belongs
994
+ * to a plugin and stays a generic block.
995
+ */
996
+ function isIframeBlock(block) {
997
+ return block._type === "iframe" && typeof block.src === "string" && Object.entries(block).every(([key, value]) => value === void 0 || (IFRAME_BLOCK_FIELDS.get(key)?.(value) ?? false));
998
+ }
999
+ /**
1000
+ * Type guard for code blocks
1001
+ */
1002
+ function isCodeBlock(block) {
1003
+ return block._type === "code";
1004
+ }
1005
+ /**
1006
+ * Convert a single Portable Text block to ProseMirror node
1007
+ */
1008
+ function convertBlock(block, path, preserveIdentity) {
1009
+ if (isTextBlock(block)) return convertTextBlock(block, preserveIdentity);
1010
+ if (isImageBlock(block)) return convertImage(block, preserveIdentity);
1011
+ if (block._type === "image") return convertMalformedImage(block, preserveIdentity);
1012
+ if (isGalleryBlock(block)) return {
1013
+ type: "gallery",
1014
+ attrs: identityAttrs({
1015
+ images: sanitizeGalleryImages(block.images),
1016
+ columns: typeof block.columns === "number" ? block.columns : void 0
1017
+ }, block._key, preserveIdentity)
1018
+ };
1019
+ if (isCodeBlock(block)) return convertCodeBlock(block, preserveIdentity);
1020
+ if (block._type === "htmlBlock") return {
1021
+ type: "htmlBlock",
1022
+ attrs: identityAttrs({ ...htmlBlockFields(block) }, block._key, preserveIdentity)
1023
+ };
1024
+ if (isIframeBlock(block)) {
1025
+ const { _type, _key, ...attrs } = block;
1026
+ return {
1027
+ type: "iframeBlock",
1028
+ attrs: identityAttrs(attrs, _key, preserveIdentity)
1029
+ };
1030
+ }
1031
+ if (isPortableTextVideoBlock(block)) return {
1032
+ type: "videoBlock",
1033
+ attrs: identityAttrs(videoNodeAttrs(block), block._key, preserveIdentity)
1034
+ };
1035
+ if (block._type === "break") return {
1036
+ type: "horizontalRule",
1037
+ attrs: identityAttrs(void 0, block._key, preserveIdentity)
1038
+ };
1039
+ if (block._type === "gallery") {
1040
+ if (preserveIdentity) return {
1041
+ type: PORTABLE_TEXT_BLOCK_NODE,
1042
+ attrs: { [PORTABLE_TEXT_BLOCK_ATTR]: block }
1043
+ };
1044
+ return {
1045
+ type: "paragraph",
1046
+ content: [{
1047
+ type: "text",
1048
+ text: `[Unknown block type: ${block._type}]`,
1049
+ marks: [{ type: "code" }]
1050
+ }]
1051
+ };
1052
+ }
1053
+ if (preserveIdentity) return {
1054
+ type: PORTABLE_TEXT_BLOCK_NODE,
1055
+ attrs: { [PORTABLE_TEXT_BLOCK_ATTR]: block }
1056
+ };
1057
+ if (block._type === "table") {
1058
+ const result = portableTextTableToProseMirror(block, {
1059
+ path,
1060
+ createKey: generateKey,
1061
+ spansToInline: (content, markDefs) => convertSpans(content, markDefs, preserveIdentity)
1062
+ });
1063
+ if (!result.ok) throw new UnsafePortableTextTableError(result.reason, result.raw, result.renderFallback);
1064
+ return result.node;
1065
+ }
1066
+ return {
1067
+ type: "paragraph",
1068
+ content: [{
1069
+ type: "text",
1070
+ text: `[Unknown block type: ${block._type}]`,
1071
+ marks: [{ type: "code" }]
1072
+ }]
1073
+ };
1074
+ }
1075
+ /**
1076
+ * Convert text block to ProseMirror paragraph or heading
1077
+ */
1078
+ function convertTextBlock(block, preserveIdentity) {
1079
+ const { style = "normal", children, markDefs = [] } = block;
1080
+ const content = convertSpans(children, markDefs, preserveIdentity);
1081
+ switch (style) {
1082
+ case "h1":
1083
+ case "h2":
1084
+ case "h3":
1085
+ case "h4":
1086
+ case "h5":
1087
+ case "h6": return {
1088
+ type: "heading",
1089
+ attrs: identityAttrs({
1090
+ level: parseInt(style.substring(1), 10),
1091
+ ...block.textAlign ? { textAlign: block.textAlign } : {}
1092
+ }, block._key, preserveIdentity),
1093
+ content: content.length > 0 ? content : void 0
1094
+ };
1095
+ case "blockquote": return {
1096
+ type: "blockquote",
1097
+ attrs: identityAttrs(void 0, block._key, preserveIdentity),
1098
+ content: [{
1099
+ type: "paragraph",
1100
+ content: content.length > 0 ? content : void 0
1101
+ }]
1102
+ };
1103
+ default: return {
1104
+ type: "paragraph",
1105
+ attrs: identityAttrs(block.textAlign ? { textAlign: block.textAlign } : void 0, block._key, preserveIdentity),
1106
+ content: content.length > 0 ? content : void 0
1107
+ };
1108
+ }
1109
+ }
1110
+ /**
1111
+ * Convert list items to ProseMirror list
1112
+ */
1113
+ function convertList(items, listType, context, preserveIdentity) {
1114
+ const rootItems = [];
1115
+ let i = 0;
1116
+ while (i < items.length) {
1117
+ const item = items[i];
1118
+ if ((item.level || 1) === 1) {
1119
+ const nestedItems = [];
1120
+ i++;
1121
+ while (i < items.length && (items[i].level || 1) > 1) {
1122
+ nestedItems.push(items[i]);
1123
+ i++;
1124
+ }
1125
+ rootItems.push(convertListItem(item, nestedItems, listType, `${context}:${rootItems.length}`, preserveIdentity));
1126
+ } else {
1127
+ rootItems.push(convertListItem(item, [], listType, `${context}:${rootItems.length}`, preserveIdentity));
1128
+ i++;
1129
+ }
1130
+ }
1131
+ const metadata = listType === "number" ? getListMetadata(items[0], `${context}:${items[0]._key}`) : void 0;
1132
+ return {
1133
+ type: listType === "bullet" ? "bulletList" : "orderedList",
1134
+ attrs: metadata ? {
1135
+ ...metadata,
1136
+ start: metadata.listStart ?? 1
1137
+ } : void 0,
1138
+ content: rootItems
1139
+ };
1140
+ }
1141
+ /**
1142
+ * Convert a single list item to ProseMirror
1143
+ */
1144
+ function convertListItem(item, nestedItems, parentListType, context, preserveIdentity) {
1145
+ const content = [];
1146
+ const spans = convertSpans(item.children, item.markDefs || [], preserveIdentity);
1147
+ content.push({
1148
+ type: "paragraph",
1149
+ attrs: identityAttrs(void 0, item._key, preserveIdentity),
1150
+ content: spans.length > 0 ? spans : void 0
1151
+ });
1152
+ if (nestedItems.length > 0) {
1153
+ let minLevel = Infinity;
1154
+ for (const ni of nestedItems) {
1155
+ const level = ni.level || 2;
1156
+ if (level < minLevel) minLevel = level;
1157
+ }
1158
+ let j = 0;
1159
+ while (j < nestedItems.length) {
1160
+ const anchorType = nestedItems[j].listItem || parentListType;
1161
+ const anchorId = anchorType === "number" ? normalizeListId(nestedItems[j].listId) : void 0;
1162
+ const nestedGroup = [];
1163
+ do {
1164
+ nestedGroup.push(nestedItems[j]);
1165
+ j++;
1166
+ } while (j < nestedItems.length && belongsToNestedGroup(nestedItems[j], minLevel, parentListType, anchorType, anchorId));
1167
+ if (nestedGroup.length > 0) {
1168
+ const adjustedGroup = nestedGroup.map((ni) => ({
1169
+ ...ni,
1170
+ level: (ni.level || 2) - 1
1171
+ }));
1172
+ content.push(convertList(adjustedGroup, anchorType, `${context}:nested:${j - nestedGroup.length}`, preserveIdentity));
1173
+ }
1174
+ }
1175
+ }
1176
+ return {
1177
+ type: "listItem",
1178
+ content
1179
+ };
1180
+ }
1181
+ /**
1182
+ * Convert Portable Text spans to ProseMirror text nodes
1183
+ */
1184
+ function convertSpans(spans, markDefs, preserveIdentity) {
1185
+ const nodes = [];
1186
+ const markDefsMap = new Map(markDefs.map((md) => [md._key, md]));
1187
+ for (const span of spans) {
1188
+ if (span._type !== "span") continue;
1189
+ const referencedMarkDefs = markDefs.filter((markDef) => span.marks?.includes(markDef._key));
1190
+ const parts = span.text.split("\n");
1191
+ for (let i = 0; i < parts.length; i++) {
1192
+ const text = parts[i];
1193
+ if (text.length > 0) {
1194
+ const marks = convertMarks(span.marks || [], markDefsMap);
1195
+ if (preserveIdentity) marks.push({
1196
+ type: PORTABLE_TEXT_SPAN_MARK,
1197
+ attrs: {
1198
+ key: span._key,
1199
+ markDefs: referencedMarkDefs
1200
+ }
1201
+ });
1202
+ const node = {
1203
+ type: "text",
1204
+ text
1205
+ };
1206
+ if (marks.length > 0) node.marks = marks;
1207
+ nodes.push(node);
1208
+ }
1209
+ if (i < parts.length - 1) nodes.push(preserveIdentity ? {
1210
+ type: "hardBreak",
1211
+ marks: [{
1212
+ type: PORTABLE_TEXT_SPAN_MARK,
1213
+ attrs: {
1214
+ key: span._key,
1215
+ markDefs: referencedMarkDefs
1216
+ }
1217
+ }]
1218
+ } : { type: "hardBreak" });
1219
+ }
1220
+ }
1221
+ return nodes;
1222
+ }
1223
+ /**
1224
+ * Convert Portable Text marks to ProseMirror marks
1225
+ */
1226
+ function convertMarks(marks, markDefs) {
1227
+ const pmMarks = [];
1228
+ for (const mark of marks) switch (mark) {
1229
+ case "strong":
1230
+ pmMarks.push({ type: "bold" });
1231
+ break;
1232
+ case "em":
1233
+ pmMarks.push({ type: "italic" });
1234
+ break;
1235
+ case "underline":
1236
+ pmMarks.push({ type: "underline" });
1237
+ break;
1238
+ case "strike-through":
1239
+ pmMarks.push({ type: "strike" });
1240
+ break;
1241
+ case "subscript":
1242
+ pmMarks.push({ type: "subscript" });
1243
+ break;
1244
+ case "superscript":
1245
+ pmMarks.push({ type: "superscript" });
1246
+ break;
1247
+ case "code":
1248
+ pmMarks.push({ type: "code" });
1249
+ break;
1250
+ default: {
1251
+ const markDef = markDefs.get(mark);
1252
+ if (markDef?._type === "link") pmMarks.push({
1253
+ type: "link",
1254
+ attrs: {
1255
+ href: markDef.href,
1256
+ target: markDef.blank ? "_blank" : null
1257
+ }
1258
+ });
1259
+ else throw new UnsupportedPortableTextMarksError([typeof markDef?._type === "string" ? markDef._type : mark]);
1260
+ break;
1261
+ }
1262
+ }
1263
+ return pmMarks;
1264
+ }
1265
+ function imageAlignment(value) {
1266
+ return value === "left" || value === "center" || value === "right" || value === "wide" || value === "full" ? value : void 0;
1267
+ }
1268
+ function imageDimension(value) {
1269
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
1270
+ }
1271
+ /**
1272
+ * Convert image block to ProseMirror
1273
+ */
1274
+ function convertImage(block, preserveIdentity) {
1275
+ const { asset, alt, width, height } = resolveImageMedia(block);
1276
+ return {
1277
+ type: "image",
1278
+ attrs: identityAttrs({
1279
+ src: asset.url || asset._ref,
1280
+ alt: alt || "",
1281
+ title: block.title || "",
1282
+ caption: Object.hasOwn(block, "caption") ? block.caption || "" : block.title || "",
1283
+ mediaId: asset._ref,
1284
+ provider: asset.provider,
1285
+ width: imageDimension(width),
1286
+ height: imageDimension(height),
1287
+ displayWidth: imageDimension(block.displayWidth),
1288
+ displayHeight: imageDimension(block.displayHeight),
1289
+ alignment: imageAlignment(block.alignment),
1290
+ link: normalizeImageLink(block.link)
1291
+ }, block._key, preserveIdentity)
1292
+ };
1293
+ }
1294
+ /**
1295
+ * Convert a malformed image block (missing `asset` wrapper) to ProseMirror.
1296
+ * Handles blocks like `{ _type: "image", url: "...", alt: "..." }` that may
1297
+ * originate from migrations or third-party imports.
1298
+ */
1299
+ function convertMalformedImage(block, preserveIdentity) {
1300
+ const url = "url" in block && typeof block.url === "string" ? block.url : "";
1301
+ const alt = "alt" in block && typeof block.alt === "string" ? block.alt : "";
1302
+ const title = "title" in block && typeof block.title === "string" ? block.title : "";
1303
+ const rawCaption = "caption" in block ? block.caption : void 0;
1304
+ return {
1305
+ type: "image",
1306
+ attrs: identityAttrs({
1307
+ src: url,
1308
+ alt,
1309
+ title,
1310
+ caption: Object.hasOwn(block, "caption") ? typeof rawCaption === "string" ? rawCaption : "" : title,
1311
+ mediaId: void 0,
1312
+ provider: void 0,
1313
+ width: imageDimension("width" in block ? block.width : void 0),
1314
+ height: imageDimension("height" in block ? block.height : void 0),
1315
+ displayWidth: imageDimension("displayWidth" in block ? block.displayWidth : void 0),
1316
+ displayHeight: imageDimension("displayHeight" in block ? block.displayHeight : void 0),
1317
+ alignment: imageAlignment("alignment" in block ? block.alignment : void 0)
1318
+ }, block._key, preserveIdentity)
1319
+ };
1320
+ }
1321
+ /**
1322
+ * Convert code block to ProseMirror
1323
+ */
1324
+ function convertCodeBlock(block, preserveIdentity) {
1325
+ return {
1326
+ type: "codeBlock",
1327
+ attrs: identityAttrs({ language: block.language || null }, block._key, preserveIdentity),
1328
+ content: block.code ? [{
1329
+ type: "text",
1330
+ text: block.code
1331
+ }] : void 0
1332
+ };
1333
+ }
1334
+
1335
+ //#endregion
1336
+ //#region src/plugins/define-plugin.ts
1337
+ /**
1338
+ * definePlugin() Helper
1339
+ *
1340
+ * Native plugin authoring entry. Returns a fully-resolved
1341
+ * `ResolvedPlugin` ready for the host integration to mount.
1342
+ *
1343
+ * Sandboxed plugins do NOT use this function. They default-export
1344
+ * a bare `{ hooks?, routes? }` object with a `satisfies SandboxedPlugin`
1345
+ * annotation from `emdash/plugin`. See the `emdash` changeset for the
1346
+ * authoring shape.
1347
+ */
1348
+ const MCP_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
1349
+ /**
1350
+ * Define a native EmDash plugin.
1351
+ *
1352
+ * Native plugins ship as regular npm modules, get installed via
1353
+ * `pnpm add` + an `astro.config.mjs` edit, and run in the host
1354
+ * process. They have full access to the runtime — capabilities are
1355
+ * still enforced by `PluginContextFactory`, but there is no isolation
1356
+ * boundary.
1357
+ *
1358
+ * @example
1359
+ * ```typescript
1360
+ * import { definePlugin } from "emdash";
1361
+ *
1362
+ * export default definePlugin({
1363
+ * id: "my-plugin",
1364
+ * version: "1.0.0",
1365
+ * capabilities: ["content:read"],
1366
+ * hooks: {
1367
+ * "content:beforeSave": async (event, ctx) => {
1368
+ * ctx.log.info("Saving content", { collection: event.collection });
1369
+ * return event.content;
1370
+ * }
1371
+ * },
1372
+ * routes: {
1373
+ * "sync": {
1374
+ * handler: async (ctx) => {
1375
+ * return { status: "ok" };
1376
+ * }
1377
+ * }
1378
+ * }
1379
+ * });
1380
+ * ```
1381
+ *
1382
+ * Sandboxed-format plugins do not use `definePlugin`. They
1383
+ * default-export a bare `{ hooks?, routes? }` object with a
1384
+ * `satisfies SandboxedPlugin` annotation from `emdash/plugin`. Calling
1385
+ * `definePlugin` with an object that has no `id` throws at runtime
1386
+ * (the type system already rejects it at compile time — this check is
1387
+ * for callers that bypass typechecking). Passing a plugin descriptor
1388
+ * (an object with an `entrypoint`) also throws.
1389
+ */
1390
+ function definePlugin(definition) {
1391
+ if (typeof definition.id !== "string" || definition.id.length === 0) throw new Error(`definePlugin() requires \`id\` (got ${typeof definition.id}). For native plugins, make sure your definition has both \`id\` and \`version\`. For sandboxed plugins, drop \`definePlugin()\` entirely and \`export default { hooks, routes } satisfies SandboxedPlugin\` from "emdash/plugin" — identity comes from \`emdash-plugin.jsonc\`.`);
1392
+ if ("entrypoint" in definition) throw new Error(`definePlugin() received a plugin descriptor for "${definition.id}" (it has an \`entrypoint\`). Pass the descriptor directly to the \`plugins\` array of the emdash() integration instead of wrapping it in definePlugin().`);
1393
+ return defineNativePlugin(definition);
1394
+ }
1395
+ function definePluginRoute(route) {
1396
+ return route;
1397
+ }
1398
+ /**
1399
+ * Internal: define a native-format plugin with full validation and normalization.
1400
+ */
1401
+ function defineNativePlugin(definition) {
1402
+ const SIMPLE_ID = /^[a-z0-9-]+$/;
1403
+ const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
1404
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
1405
+ const { id, version, capabilities = [], allowedHosts = [], hooks = {}, routes = {}, mcp = { tools: {} }, admin = {} } = definition;
1406
+ const storage = definition.storage ?? {};
1407
+ if (!SIMPLE_ID.test(id) && !SCOPED_ID.test(id)) throw new Error(`Invalid plugin id "${id}". Must be lowercase alphanumeric with dashes (e.g., "my-plugin" or "@scope/my-plugin").`);
1408
+ if (!SEMVER_PATTERN.test(version)) throw new Error(`Invalid plugin version "${version}". Must be semver format (e.g., "1.0.0").`);
1409
+ for (const [name, tool] of Object.entries(mcp.tools)) {
1410
+ if (!MCP_TOOL_NAME_PATTERN.test(name)) throw new Error(`Invalid MCP tool name "${name}" in plugin "${id}".`);
1411
+ const route = routes[tool.route];
1412
+ if (!route) throw new Error(`MCP tool "${name}" references unknown route "${tool.route}".`);
1413
+ if (route.public) throw new Error(`MCP tool "${name}" cannot reference a public route.`);
1414
+ if (route.response === "raw") throw new Error(`MCP tool "${name}" cannot reference a raw response route.`);
1415
+ if (!isJsonPostRouteContract(route)) throw new Error(`MCP tool "${name}" must reference a POST-compatible JSON route.`);
1416
+ if (!route.permission) throw new Error(`MCP route "${tool.route}" must declare a permission.`);
1417
+ }
1418
+ for (const [kind, extensions] of [["editor panel", admin.editorPanels], ["editor action", admin.editorActions]]) for (const extension of extensions ?? []) {
1419
+ const route = routes[extension.route];
1420
+ if (!route) throw new Error(`Plugin ${kind} "${extension.id}" references unknown route "${extension.route}".`);
1421
+ if (route.public) throw new Error(`Plugin ${kind} "${extension.id}" must reference a private route.`);
1422
+ if (!isJsonPostRouteContract(route)) throw new Error(`Plugin ${kind} "${extension.id}" must reference a route that accepts POST JSON requests and returns JSON.`);
1423
+ }
1424
+ if ((admin.pages?.length ?? 0) > 0 || (admin.widgets?.length ?? 0) > 0) {
1425
+ const adminRoute = routes.admin;
1426
+ if (adminRoute && (adminRoute.public === true || !isJsonPostRouteContract(adminRoute))) throw new Error("Block Kit admin route must accept POST JSON requests and return JSON.");
1427
+ }
1428
+ const validCapabilities = new Set(PLUGIN_CAPABILITIES);
1429
+ for (const cap of capabilities) if (!validCapabilities.has(cap)) throw new Error(`Invalid capability "${cap}" in plugin "${id}".`);
1430
+ warnDeprecatedPluginCapabilities(id, capabilities);
1431
+ return {
1432
+ id,
1433
+ version,
1434
+ capabilities: normalizePluginCapabilities(capabilities),
1435
+ allowedHosts,
1436
+ storage,
1437
+ hooks: resolveHooks(hooks, id),
1438
+ routes,
1439
+ mcp,
1440
+ admin
1441
+ };
1442
+ }
1443
+ /**
1444
+ * Resolve hooks to normalized format with defaults.
1445
+ *
1446
+ * PluginHooks and ResolvedPluginHooks share the same keys — each input value is
1447
+ * `HookConfig<H> | H` and the output is `ResolvedHook<H>`. TS can't narrow
1448
+ * the handler type through a dynamic key, so we assert at the loop boundary.
1449
+ */
1450
+ function resolveHooks(hooks, pluginId) {
1451
+ const resolved = {};
1452
+ for (const key of Object.keys(hooks)) {
1453
+ const hook = hooks[key];
1454
+ if (hook) resolved[key] = resolveHook(hook, pluginId);
1455
+ }
1456
+ return resolved;
1457
+ }
1458
+ /**
1459
+ * Check if a hook value is a config object (has a `handler` property)
1460
+ */
1461
+ function isHookConfig(hook) {
1462
+ return typeof hook === "object" && hook !== null && "handler" in hook;
1463
+ }
1464
+ /**
1465
+ * Resolve a single hook to normalized format
1466
+ */
1467
+ function resolveHook(hook, pluginId) {
1468
+ if (isHookConfig(hook)) {
1469
+ if (hook.exclusive !== void 0 && typeof hook.exclusive !== "boolean") throw new Error(`Invalid "exclusive" value in hook config for plugin "${pluginId}". Must be boolean.`);
1470
+ return {
1471
+ priority: hook.priority ?? 100,
1472
+ timeout: hook.timeout ?? 5e3,
1473
+ dependencies: hook.dependencies ?? [],
1474
+ errorPolicy: hook.errorPolicy ?? "abort",
1475
+ exclusive: hook.exclusive ?? false,
1476
+ handler: hook.handler,
1477
+ pluginId
1478
+ };
1479
+ }
1480
+ return {
1481
+ priority: 100,
1482
+ timeout: 5e3,
1483
+ dependencies: [],
1484
+ errorPolicy: "abort",
1485
+ exclusive: false,
1486
+ handler: hook,
1487
+ pluginId
1488
+ };
1489
+ }
1490
+
1491
+ //#endregion
1492
+ //#region src/plugins/hooks.ts
1493
+ /**
1494
+ * Plugin Hooks System v2
1495
+ *
1496
+ * Uses the unified PluginContext for all hooks.
1497
+ * Manages lifecycle hooks with:
1498
+ * - Deterministic ordering via priority + dependencies
1499
+ * - Timeout enforcement
1500
+ * - Error isolation
1501
+ * - Observability
1502
+ *
1503
+ */
1504
+ const CONTENT_SAVE_HOOK_CONTEXT_KEY = Symbol.for("emdash:content-save-hook-context");
1505
+ const contentSaveHookContext = globalThis[CONTENT_SAVE_HOOK_CONTEXT_KEY] ?? new AsyncLocalStorage();
1506
+ globalThis[CONTENT_SAVE_HOOK_CONTEXT_KEY] = contentSaveHookContext;
1507
+ function getActiveContentSaveHookName() {
1508
+ return contentSaveHookContext.getStore();
1509
+ }
1510
+ function assignSaveHookDetails(event, details) {
1511
+ if (details.locale !== void 0) event.locale = details.locale;
1512
+ if (details.translationOf !== void 0) event.translationOf = details.translationOf;
1513
+ }
1514
+ /**
1515
+ * Hook pipeline for executing hooks in order
1516
+ */
1517
+ var HookPipeline = class HookPipeline {
1518
+ hooks = /* @__PURE__ */ new Map();
1519
+ pluginMap = /* @__PURE__ */ new Map();
1520
+ contextFactory = null;
1521
+ /** Stored so setContextFactory can merge incrementally. */
1522
+ contextFactoryOptions = {};
1523
+ /** Hook names where at least one handler declared exclusive: true */
1524
+ exclusiveHookNames = /* @__PURE__ */ new Set();
1525
+ /**
1526
+ * Selected provider plugin ID for each exclusive hook.
1527
+ * Set by the PluginManager after resolution.
1528
+ */
1529
+ exclusiveSelections = /* @__PURE__ */ new Map();
1530
+ constructor(plugins, factoryOptions) {
1531
+ if (factoryOptions) {
1532
+ this.contextFactory = new PluginContextFactory(factoryOptions);
1533
+ this.contextFactoryOptions = { ...factoryOptions };
1534
+ }
1535
+ for (const plugin of plugins) this.pluginMap.set(plugin.id, plugin);
1536
+ this.registerPlugins(plugins);
1537
+ }
1538
+ /**
1539
+ * Set or update the context factory options.
1540
+ *
1541
+ * When called on a pipeline that already has a factory, the new options
1542
+ * are merged on top of the existing ones so that callers don't need to
1543
+ * repeat every field (e.g. adding `cronReschedule` without losing
1544
+ * `storage` / `getUploadUrl`).
1545
+ */
1546
+ setContextFactory(options) {
1547
+ const merged = {
1548
+ ...this.contextFactoryOptions,
1549
+ ...options
1550
+ };
1551
+ this.contextFactory = new PluginContextFactory(merged);
1552
+ this.contextFactoryOptions = merged;
1553
+ }
1554
+ /**
1555
+ * Get context for a plugin
1556
+ */
1557
+ getContext(pluginId) {
1558
+ const plugin = this.pluginMap.get(pluginId);
1559
+ if (!plugin) throw new Error(`Plugin "${pluginId}" not found`);
1560
+ if (!this.contextFactory) throw new Error("Context factory not initialized - call setContextFactory first");
1561
+ return this.contextFactory.createContext(plugin);
1562
+ }
1563
+ /**
1564
+ * Get typed hooks for a specific hook name.
1565
+ * The internal map stores ResolvedHook<unknown>, but we know each name
1566
+ * maps to a specific handler type via HookHandlerMap.
1567
+ *
1568
+ * Exclusive hooks that have a selected provider are filtered out — they
1569
+ * should only run via invokeExclusiveHook(), not in the regular pipeline.
1570
+ */
1571
+ getTypedHooks(name) {
1572
+ const all = this.hooks.get(name) ?? [];
1573
+ if (this.exclusiveSelections.has(name)) return all.filter((h) => !h.exclusive);
1574
+ return all;
1575
+ }
1576
+ /**
1577
+ * Register all hooks from plugins.
1578
+ *
1579
+ * Registers each hook name individually to preserve type safety. The
1580
+ * internal map stores ResolvedHook<unknown> since it's keyed by string,
1581
+ * but getTypedHooks() restores the correct handler type on retrieval.
1582
+ */
1583
+ registerPlugins(plugins) {
1584
+ for (const plugin of plugins) {
1585
+ this.registerPluginHook(plugin, "plugin:install");
1586
+ this.registerPluginHook(plugin, "plugin:activate");
1587
+ this.registerPluginHook(plugin, "plugin:deactivate");
1588
+ this.registerPluginHook(plugin, "plugin:uninstall");
1589
+ this.registerPluginHook(plugin, "content:beforeSave");
1590
+ this.registerPluginHook(plugin, "content:afterSave");
1591
+ this.registerPluginHook(plugin, "content:beforeDelete");
1592
+ this.registerPluginHook(plugin, "content:afterDelete");
1593
+ this.registerPluginHook(plugin, "content:beforePublish");
1594
+ this.registerPluginHook(plugin, "content:beforeSchedule");
1595
+ this.registerPluginHook(plugin, "content:beforeUnpublish");
1596
+ this.registerPluginHook(plugin, "content:afterPublish");
1597
+ this.registerPluginHook(plugin, "content:afterUnpublish");
1598
+ this.registerPluginHook(plugin, "content:afterRestore");
1599
+ this.registerPluginHook(plugin, "content:afterSchedule");
1600
+ this.registerPluginHook(plugin, "content:afterUnschedule");
1601
+ this.registerPluginHook(plugin, "media:beforeUpload");
1602
+ this.registerPluginHook(plugin, "media:afterUpload");
1603
+ this.registerPluginHook(plugin, "cron");
1604
+ this.registerPluginHook(plugin, "email:beforeSend");
1605
+ this.registerPluginHook(plugin, "email:deliver");
1606
+ this.registerPluginHook(plugin, "email:afterSend");
1607
+ this.registerPluginHook(plugin, "comment:beforeCreate");
1608
+ this.registerPluginHook(plugin, "comment:moderate");
1609
+ this.registerPluginHook(plugin, "comment:afterCreate");
1610
+ this.registerPluginHook(plugin, "comment:afterModerate");
1611
+ this.registerPluginHook(plugin, "byline:afterSave");
1612
+ this.registerPluginHook(plugin, "byline:afterDelete");
1613
+ this.registerPluginHook(plugin, "page:metadata");
1614
+ this.registerPluginHook(plugin, "page:fragments");
1615
+ }
1616
+ for (const [hookName, hooks] of this.hooks) this.hooks.set(hookName, this.sortHooks(hooks));
1617
+ }
1618
+ /**
1619
+ * Maps hook names to the capability required to register them.
1620
+ *
1621
+ * Hooks not listed here have no capability requirement (e.g. lifecycle
1622
+ * hooks, cron). Any plugin declaring a listed hook without the required
1623
+ * capability will have that hook silently skipped at registration time.
1624
+ */
1625
+ static HOOK_REQUIRED_CAPABILITY = new Map([
1626
+ ["email:beforeSend", "hooks.email-events:register"],
1627
+ ["email:afterSend", "hooks.email-events:register"],
1628
+ ["email:deliver", "hooks.email-transport:register"],
1629
+ ["content:beforeSave", "content:write"],
1630
+ ["content:afterSave", "content:read"],
1631
+ ["content:beforeDelete", "content:read"],
1632
+ ["content:afterDelete", "content:read"],
1633
+ ["content:beforePublish", "hooks.content-policy:register"],
1634
+ ["content:beforeSchedule", "hooks.content-policy:register"],
1635
+ ["content:beforeUnpublish", "hooks.content-policy:register"],
1636
+ ["content:afterPublish", "content:read"],
1637
+ ["content:afterUnpublish", "content:read"],
1638
+ ["content:afterRestore", "content:read"],
1639
+ ["content:afterSchedule", "content:read"],
1640
+ ["content:afterUnschedule", "content:read"],
1641
+ ["media:beforeUpload", "media:write"],
1642
+ ["media:afterUpload", "media:read"],
1643
+ ["comment:beforeCreate", "users:read"],
1644
+ ["comment:moderate", "users:read"],
1645
+ ["comment:afterCreate", "users:read"],
1646
+ ["comment:afterModerate", "users:read"],
1647
+ ["byline:afterSave", "bylines:read"],
1648
+ ["byline:afterDelete", "bylines:read"],
1649
+ ["page:fragments", "hooks.page-fragments:register"]
1650
+ ]);
1651
+ /**
1652
+ * Register a single plugin's hook by name
1653
+ */
1654
+ registerPluginHook(plugin, name) {
1655
+ const hook = plugin.hooks[name];
1656
+ if (!hook) return;
1657
+ const requiredCapability = HookPipeline.HOOK_REQUIRED_CAPABILITY.get(name);
1658
+ if (requiredCapability && !plugin.capabilities.includes(requiredCapability)) {
1659
+ console.warn(`[hooks] Plugin "${plugin.id}" declares ${name} hook without ${requiredCapability} capability — skipping`);
1660
+ return;
1661
+ }
1662
+ if (hook.exclusive) this.exclusiveHookNames.add(name);
1663
+ this.registerHook(name, hook);
1664
+ }
1665
+ /**
1666
+ * Register a single hook
1667
+ */
1668
+ registerHook(name, hook) {
1669
+ const existing = this.hooks.get(name) || [];
1670
+ existing.push(hook);
1671
+ this.hooks.set(name, existing);
1672
+ }
1673
+ /**
1674
+ * Sort hooks by priority and dependencies
1675
+ */
1676
+ sortHooks(hooks) {
1677
+ const sorted = [];
1678
+ const remaining = [...hooks];
1679
+ while (remaining.length > 0) {
1680
+ const ready = remaining.filter((hook) => hook.dependencies.every((dep) => sorted.some((s) => s.pluginId === dep)));
1681
+ if (ready.length === 0) {
1682
+ const pluginIds = remaining.map((h) => h.pluginId).join(", ");
1683
+ console.warn(`[hooks] Hook dependency cycle or missing dependency detected among plugins: ${pluginIds}. Falling back to priority order.`);
1684
+ remaining.sort((a, b) => a.priority - b.priority);
1685
+ sorted.push(...remaining);
1686
+ break;
1687
+ }
1688
+ ready.sort((a, b) => a.priority - b.priority);
1689
+ const next = ready[0];
1690
+ sorted.push(next);
1691
+ remaining.splice(remaining.indexOf(next), 1);
1692
+ }
1693
+ return sorted;
1694
+ }
1695
+ /**
1696
+ * Execute a hook with timeout
1697
+ */
1698
+ async executeWithTimeout(fn, timeout) {
1699
+ let timer;
1700
+ const timeoutPromise = new Promise((_, reject) => timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Hook timeout after ${timeout}ms`)), timeout));
1701
+ try {
1702
+ return await Promise.race([fn(), timeoutPromise]);
1703
+ } finally {
1704
+ clearTimeout(timer);
1705
+ }
1706
+ }
1707
+ /**
1708
+ * Run plugin:install hooks
1709
+ */
1710
+ async runPluginInstall(pluginId) {
1711
+ return this.runLifecycleHook("plugin:install", pluginId);
1712
+ }
1713
+ /**
1714
+ * Run plugin:activate hooks
1715
+ */
1716
+ async runPluginActivate(pluginId) {
1717
+ return this.runLifecycleHook("plugin:activate", pluginId);
1718
+ }
1719
+ /**
1720
+ * Run plugin:deactivate hooks
1721
+ */
1722
+ async runPluginDeactivate(pluginId) {
1723
+ return this.runLifecycleHook("plugin:deactivate", pluginId);
1724
+ }
1725
+ /**
1726
+ * Run plugin:uninstall hooks
1727
+ */
1728
+ async runPluginUninstall(pluginId, deleteData) {
1729
+ const hooks = this.getTypedHooks("plugin:uninstall");
1730
+ const results = [];
1731
+ const hook = hooks.find((h) => h.pluginId === pluginId);
1732
+ if (!hook) return results;
1733
+ const { handler } = hook;
1734
+ const event = { deleteData };
1735
+ const ctx = this.getContext(pluginId);
1736
+ const start = Date.now();
1737
+ try {
1738
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1739
+ results.push({
1740
+ success: true,
1741
+ pluginId: hook.pluginId,
1742
+ duration: Date.now() - start
1743
+ });
1744
+ } catch (error) {
1745
+ results.push({
1746
+ success: false,
1747
+ error: error instanceof Error ? error : new Error(String(error)),
1748
+ pluginId: hook.pluginId,
1749
+ duration: Date.now() - start
1750
+ });
1751
+ }
1752
+ return results;
1753
+ }
1754
+ async runLifecycleHook(hookName, pluginId) {
1755
+ const hooks = this.getTypedHooks(hookName);
1756
+ const results = [];
1757
+ const hook = hooks.find((h) => h.pluginId === pluginId);
1758
+ if (!hook) return results;
1759
+ const { handler } = hook;
1760
+ const event = {};
1761
+ const ctx = this.getContext(pluginId);
1762
+ const start = Date.now();
1763
+ try {
1764
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1765
+ results.push({
1766
+ success: true,
1767
+ pluginId: hook.pluginId,
1768
+ duration: Date.now() - start
1769
+ });
1770
+ } catch (error) {
1771
+ results.push({
1772
+ success: false,
1773
+ error: error instanceof Error ? error : new Error(String(error)),
1774
+ pluginId: hook.pluginId,
1775
+ duration: Date.now() - start
1776
+ });
1777
+ }
1778
+ return results;
1779
+ }
1780
+ /**
1781
+ * Run content:beforeSave hooks
1782
+ * Returns modified content from the pipeline. `id` is the existing item's
1783
+ * ID when updating. `actor` is the authenticated user that triggered the
1784
+ * save, when one is available.
1785
+ */
1786
+ async runContentBeforeSave(content, collection, isNew, id, actor, details = {}) {
1787
+ const hooks = this.getTypedHooks("content:beforeSave");
1788
+ const results = [];
1789
+ let currentContent = content;
1790
+ for (const hook of hooks) {
1791
+ const { handler } = hook;
1792
+ const event = {
1793
+ content: currentContent,
1794
+ collection,
1795
+ isNew
1796
+ };
1797
+ if (id !== void 0) event.id = id;
1798
+ if (actor !== void 0) event.actor = { ...actor };
1799
+ assignSaveHookDetails(event, details);
1800
+ const ctx = this.getContext(hook.pluginId);
1801
+ const start = Date.now();
1802
+ try {
1803
+ const result = await contentSaveHookContext.run("content:beforeSave", () => this.executeWithTimeout(() => handler(event, ctx), hook.timeout));
1804
+ if (result !== void 0) currentContent = result;
1805
+ results.push({
1806
+ success: true,
1807
+ value: currentContent,
1808
+ pluginId: hook.pluginId,
1809
+ duration: Date.now() - start
1810
+ });
1811
+ } catch (error) {
1812
+ results.push({
1813
+ success: false,
1814
+ error: error instanceof Error ? error : new Error(String(error)),
1815
+ pluginId: hook.pluginId,
1816
+ duration: Date.now() - start
1817
+ });
1818
+ if (hook.errorPolicy === "abort") throw error;
1819
+ }
1820
+ }
1821
+ return {
1822
+ content: currentContent,
1823
+ results
1824
+ };
1825
+ }
1826
+ /**
1827
+ * Run content:afterSave hooks
1828
+ */
1829
+ async runContentAfterSave(content, collection, isNew, actor, excludePluginId, details = {}) {
1830
+ const hooks = this.getTypedHooks("content:afterSave");
1831
+ const results = [];
1832
+ for (const hook of hooks) {
1833
+ if (hook.pluginId === excludePluginId) continue;
1834
+ const { handler } = hook;
1835
+ const event = {
1836
+ content,
1837
+ collection,
1838
+ isNew
1839
+ };
1840
+ if (actor !== void 0) event.actor = { ...actor };
1841
+ assignSaveHookDetails(event, details);
1842
+ const ctx = this.getContext(hook.pluginId);
1843
+ const start = Date.now();
1844
+ try {
1845
+ await contentSaveHookContext.run("content:afterSave", () => this.executeWithTimeout(() => handler(event, ctx), hook.timeout));
1846
+ results.push({
1847
+ success: true,
1848
+ pluginId: hook.pluginId,
1849
+ duration: Date.now() - start
1850
+ });
1851
+ } catch (error) {
1852
+ results.push({
1853
+ success: false,
1854
+ error: error instanceof Error ? error : new Error(String(error)),
1855
+ pluginId: hook.pluginId,
1856
+ duration: Date.now() - start
1857
+ });
1858
+ if (hook.errorPolicy === "abort") throw error;
1859
+ }
1860
+ }
1861
+ return results;
1862
+ }
1863
+ /**
1864
+ * Run content:beforeDelete hooks
1865
+ * Returns whether deletion is allowed
1866
+ */
1867
+ async runContentBeforeDelete(id, collection) {
1868
+ const hooks = this.getTypedHooks("content:beforeDelete");
1869
+ const results = [];
1870
+ let allowed = true;
1871
+ for (const hook of hooks) {
1872
+ const { handler } = hook;
1873
+ const event = {
1874
+ id,
1875
+ collection,
1876
+ permanent: false
1877
+ };
1878
+ const ctx = this.getContext(hook.pluginId);
1879
+ const start = Date.now();
1880
+ try {
1881
+ const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1882
+ if (result === false) allowed = false;
1883
+ results.push({
1884
+ success: true,
1885
+ value: result !== false,
1886
+ pluginId: hook.pluginId,
1887
+ duration: Date.now() - start
1888
+ });
1889
+ } catch (error) {
1890
+ results.push({
1891
+ success: false,
1892
+ error: error instanceof Error ? error : new Error(String(error)),
1893
+ pluginId: hook.pluginId,
1894
+ duration: Date.now() - start
1895
+ });
1896
+ if (hook.errorPolicy === "abort") throw error;
1897
+ }
1898
+ }
1899
+ return {
1900
+ allowed,
1901
+ results
1902
+ };
1903
+ }
1904
+ /**
1905
+ * Run content:afterDelete hooks
1906
+ */
1907
+ async runContentAfterDelete(id, collection, permanent) {
1908
+ const hooks = this.getTypedHooks("content:afterDelete");
1909
+ const results = [];
1910
+ for (const hook of hooks) {
1911
+ const { handler } = hook;
1912
+ const event = {
1913
+ id,
1914
+ collection,
1915
+ permanent
1916
+ };
1917
+ const ctx = this.getContext(hook.pluginId);
1918
+ const start = Date.now();
1919
+ try {
1920
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1921
+ results.push({
1922
+ success: true,
1923
+ pluginId: hook.pluginId,
1924
+ duration: Date.now() - start
1925
+ });
1926
+ } catch (error) {
1927
+ results.push({
1928
+ success: false,
1929
+ error: error instanceof Error ? error : new Error(String(error)),
1930
+ pluginId: hook.pluginId,
1931
+ duration: Date.now() - start
1932
+ });
1933
+ if (hook.errorPolicy === "abort") throw error;
1934
+ }
1935
+ }
1936
+ return results;
1937
+ }
1938
+ async runContentPolicy(name, event) {
1939
+ if (name === "content:beforeSchedule") {
1940
+ if (!("scheduledAt" in event)) throw new Error("content:beforeSchedule requires scheduledAt");
1941
+ return this.runContentPolicyHooks(name, this.getTypedHooks(name), event);
1942
+ }
1943
+ return this.runContentPolicyHooks(name, this.getTypedHooks(name), event);
1944
+ }
1945
+ async runContentPolicyHooks(name, hooks, event) {
1946
+ const results = [];
1947
+ for (const hook of hooks) {
1948
+ const ctx = this.getContext(hook.pluginId);
1949
+ const start = Date.now();
1950
+ try {
1951
+ const decision = inspectContentPolicyDecision(await this.executeWithTimeout(() => hook.handler(event, ctx), hook.timeout));
1952
+ if (decision.kind === "invalid") throw new Error(`Plugin "${hook.pluginId}" returned an invalid ${name} decision`);
1953
+ results.push({
1954
+ success: true,
1955
+ pluginId: hook.pluginId,
1956
+ duration: Date.now() - start
1957
+ });
1958
+ if (decision.kind === "cancel") return {
1959
+ cancellation: {
1960
+ pluginId: hook.pluginId,
1961
+ reason: decision.reason
1962
+ },
1963
+ results
1964
+ };
1965
+ } catch (error) {
1966
+ results.push({
1967
+ success: false,
1968
+ error: error instanceof Error ? error : new Error(String(error)),
1969
+ pluginId: hook.pluginId,
1970
+ duration: Date.now() - start
1971
+ });
1972
+ if (hook.errorPolicy === "abort") throw error;
1973
+ console.error(`[content-policy] Plugin "${hook.pluginId}" failed ${name}; continuing by policy`);
1974
+ }
1975
+ }
1976
+ return { results };
1977
+ }
1978
+ /**
1979
+ * Run content state-change hooks that all share the same event shape.
1980
+ */
1981
+ async runContentStateChangeHook(name, content, collection) {
1982
+ const hooks = this.getTypedHooks(name);
1983
+ const results = [];
1984
+ for (const hook of hooks) {
1985
+ const { handler } = hook;
1986
+ const event = {
1987
+ content,
1988
+ collection
1989
+ };
1990
+ const ctx = this.getContext(hook.pluginId);
1991
+ const start = Date.now();
1992
+ try {
1993
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1994
+ results.push({
1995
+ success: true,
1996
+ pluginId: hook.pluginId,
1997
+ duration: Date.now() - start
1998
+ });
1999
+ } catch (error) {
2000
+ results.push({
2001
+ success: false,
2002
+ error: error instanceof Error ? error : new Error(String(error)),
2003
+ pluginId: hook.pluginId,
2004
+ duration: Date.now() - start
2005
+ });
2006
+ if (hook.errorPolicy === "abort") throw error;
2007
+ }
2008
+ }
2009
+ return results;
2010
+ }
2011
+ /**
2012
+ * Run content:afterPublish hooks (fire-and-forget).
2013
+ */
2014
+ async runContentAfterPublish(content, collection) {
2015
+ return this.runContentStateChangeHook("content:afterPublish", content, collection);
2016
+ }
2017
+ /**
2018
+ * Run content:afterUnpublish hooks (fire-and-forget).
2019
+ */
2020
+ async runContentAfterUnpublish(content, collection) {
2021
+ return this.runContentStateChangeHook("content:afterUnpublish", content, collection);
2022
+ }
2023
+ /**
2024
+ * Run content:afterRestore hooks (fire-and-forget).
2025
+ */
2026
+ async runContentAfterRestore(content, collection) {
2027
+ return this.runContentStateChangeHook("content:afterRestore", content, collection);
2028
+ }
2029
+ /**
2030
+ * Run content:afterSchedule hooks (fire-and-forget).
2031
+ */
2032
+ async runContentAfterSchedule(content, collection) {
2033
+ return this.runContentStateChangeHook("content:afterSchedule", content, collection);
2034
+ }
2035
+ /**
2036
+ * Run content:afterUnschedule hooks (fire-and-forget).
2037
+ */
2038
+ async runContentAfterUnschedule(content, collection) {
2039
+ return this.runContentStateChangeHook("content:afterUnschedule", content, collection);
2040
+ }
2041
+ /**
2042
+ * Run media:beforeUpload hooks
2043
+ */
2044
+ async runMediaBeforeUpload(file) {
2045
+ const hooks = this.getTypedHooks("media:beforeUpload");
2046
+ const results = [];
2047
+ let currentFile = file;
2048
+ for (const hook of hooks) {
2049
+ const { handler } = hook;
2050
+ const event = { file: currentFile };
2051
+ const ctx = this.getContext(hook.pluginId);
2052
+ const start = Date.now();
2053
+ try {
2054
+ const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2055
+ if (result !== void 0) currentFile = result;
2056
+ results.push({
2057
+ success: true,
2058
+ value: currentFile,
2059
+ pluginId: hook.pluginId,
2060
+ duration: Date.now() - start
2061
+ });
2062
+ } catch (error) {
2063
+ results.push({
2064
+ success: false,
2065
+ error: error instanceof Error ? error : new Error(String(error)),
2066
+ pluginId: hook.pluginId,
2067
+ duration: Date.now() - start
2068
+ });
2069
+ if (hook.errorPolicy === "abort") throw error;
2070
+ }
2071
+ }
2072
+ return {
2073
+ file: currentFile,
2074
+ results
2075
+ };
2076
+ }
2077
+ /**
2078
+ * Run media:afterUpload hooks
2079
+ */
2080
+ async runMediaAfterUpload(media) {
2081
+ const hooks = this.getTypedHooks("media:afterUpload");
2082
+ const results = [];
2083
+ for (const hook of hooks) {
2084
+ const { handler } = hook;
2085
+ const event = { media };
2086
+ const ctx = this.getContext(hook.pluginId);
2087
+ const start = Date.now();
2088
+ try {
2089
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2090
+ results.push({
2091
+ success: true,
2092
+ pluginId: hook.pluginId,
2093
+ duration: Date.now() - start
2094
+ });
2095
+ } catch (error) {
2096
+ results.push({
2097
+ success: false,
2098
+ error: error instanceof Error ? error : new Error(String(error)),
2099
+ pluginId: hook.pluginId,
2100
+ duration: Date.now() - start
2101
+ });
2102
+ if (hook.errorPolicy === "abort") throw error;
2103
+ }
2104
+ }
2105
+ return results;
2106
+ }
2107
+ /**
2108
+ * Invoke the cron hook for a specific plugin.
2109
+ *
2110
+ * Unlike other hooks which broadcast to all plugins, the cron hook is
2111
+ * dispatched only to the target plugin — the one that owns the task.
2112
+ */
2113
+ async invokeCronHook(pluginId, event) {
2114
+ const hook = this.getTypedHooks("cron").find((h) => h.pluginId === pluginId);
2115
+ if (!hook) return {
2116
+ success: false,
2117
+ error: /* @__PURE__ */ new Error(`Plugin "${pluginId}" has no cron hook registered`),
2118
+ pluginId,
2119
+ duration: 0
2120
+ };
2121
+ const { handler } = hook;
2122
+ const ctx = this.getContext(pluginId);
2123
+ const start = Date.now();
2124
+ try {
2125
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2126
+ return {
2127
+ success: true,
2128
+ pluginId,
2129
+ duration: Date.now() - start
2130
+ };
2131
+ } catch (error) {
2132
+ return {
2133
+ success: false,
2134
+ error: error instanceof Error ? error : new Error(String(error)),
2135
+ pluginId,
2136
+ duration: Date.now() - start
2137
+ };
2138
+ }
2139
+ }
2140
+ /**
2141
+ * Run email:beforeSend hooks (middleware pipeline).
2142
+ *
2143
+ * Each handler receives the message and returns a modified message or
2144
+ * `false` to cancel delivery. The pipeline chains message transformations —
2145
+ * each handler receives the output of the previous one.
2146
+ */
2147
+ async runEmailBeforeSend(message, source) {
2148
+ const hooks = this.getTypedHooks("email:beforeSend");
2149
+ const results = [];
2150
+ let currentMessage = message;
2151
+ for (const hook of hooks) {
2152
+ const { handler } = hook;
2153
+ const event = {
2154
+ message: { ...currentMessage },
2155
+ source
2156
+ };
2157
+ const ctx = this.getContext(hook.pluginId);
2158
+ const start = Date.now();
2159
+ try {
2160
+ const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2161
+ if (result === false) {
2162
+ results.push({
2163
+ success: true,
2164
+ value: false,
2165
+ pluginId: hook.pluginId,
2166
+ duration: Date.now() - start
2167
+ });
2168
+ return {
2169
+ message: false,
2170
+ results
2171
+ };
2172
+ }
2173
+ if (result && typeof result === "object") currentMessage = result;
2174
+ results.push({
2175
+ success: true,
2176
+ value: currentMessage,
2177
+ pluginId: hook.pluginId,
2178
+ duration: Date.now() - start
2179
+ });
2180
+ } catch (error) {
2181
+ results.push({
2182
+ success: false,
2183
+ error: error instanceof Error ? error : new Error(String(error)),
2184
+ pluginId: hook.pluginId,
2185
+ duration: Date.now() - start
2186
+ });
2187
+ if (hook.errorPolicy === "abort") throw error;
2188
+ }
2189
+ }
2190
+ return {
2191
+ message: currentMessage,
2192
+ results
2193
+ };
2194
+ }
2195
+ /**
2196
+ * Run email:afterSend hooks (fire-and-forget).
2197
+ *
2198
+ * Errors are logged but don't propagate — they don't affect the caller.
2199
+ */
2200
+ async runEmailAfterSend(message, source) {
2201
+ const hooks = this.getTypedHooks("email:afterSend");
2202
+ const results = [];
2203
+ for (const hook of hooks) {
2204
+ const { handler } = hook;
2205
+ const event = {
2206
+ message,
2207
+ source
2208
+ };
2209
+ const ctx = this.getContext(hook.pluginId);
2210
+ const start = Date.now();
2211
+ try {
2212
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2213
+ results.push({
2214
+ success: true,
2215
+ pluginId: hook.pluginId,
2216
+ duration: Date.now() - start
2217
+ });
2218
+ } catch (error) {
2219
+ console.error(`[email:afterSend] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2220
+ results.push({
2221
+ success: false,
2222
+ error: error instanceof Error ? error : new Error(String(error)),
2223
+ pluginId: hook.pluginId,
2224
+ duration: Date.now() - start
2225
+ });
2226
+ }
2227
+ }
2228
+ return results;
2229
+ }
2230
+ /**
2231
+ * Run comment:beforeCreate hooks (middleware pipeline).
2232
+ *
2233
+ * Each handler receives the event and returns a modified event or
2234
+ * `false` to reject the comment. The pipeline chains transformations —
2235
+ * each handler receives the output of the previous one.
2236
+ */
2237
+ async runCommentBeforeCreate(event) {
2238
+ const hooks = this.getTypedHooks("comment:beforeCreate");
2239
+ let currentEvent = event;
2240
+ for (const hook of hooks) {
2241
+ const { handler } = hook;
2242
+ const ctx = this.getContext(hook.pluginId);
2243
+ const start = Date.now();
2244
+ try {
2245
+ const result = await this.executeWithTimeout(() => handler({ ...currentEvent }, ctx), hook.timeout);
2246
+ if (result === false) return false;
2247
+ if (result && typeof result === "object") currentEvent = result;
2248
+ } catch (error) {
2249
+ console.error(`[comment:beforeCreate] Plugin "${hook.pluginId}" error (${Date.now() - start}ms):`, error instanceof Error ? error.message : error);
2250
+ if (hook.errorPolicy === "abort") throw error;
2251
+ }
2252
+ }
2253
+ return currentEvent;
2254
+ }
2255
+ /**
2256
+ * Run comment:afterCreate hooks (fire-and-forget).
2257
+ *
2258
+ * Errors are logged but don't propagate — they don't affect the caller.
2259
+ */
2260
+ async runCommentAfterCreate(event) {
2261
+ const hooks = this.getTypedHooks("comment:afterCreate");
2262
+ for (const hook of hooks) {
2263
+ const { handler } = hook;
2264
+ const ctx = this.getContext(hook.pluginId);
2265
+ try {
2266
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2267
+ } catch (error) {
2268
+ console.error(`[comment:afterCreate] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2269
+ }
2270
+ }
2271
+ }
2272
+ /**
2273
+ * Run comment:afterModerate hooks (fire-and-forget).
2274
+ *
2275
+ * Errors are logged but don't propagate — they don't affect the caller.
2276
+ */
2277
+ async runCommentAfterModerate(event) {
2278
+ const hooks = this.getTypedHooks("comment:afterModerate");
2279
+ for (const hook of hooks) {
2280
+ const { handler } = hook;
2281
+ const ctx = this.getContext(hook.pluginId);
2282
+ try {
2283
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2284
+ } catch (error) {
2285
+ console.error(`[comment:afterModerate] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2286
+ }
2287
+ }
2288
+ }
2289
+ /** Run byline:afterSave hooks. Errors are logged and never propagate. */
2290
+ async runBylineAfterSave(byline, isNew) {
2291
+ const event = {
2292
+ byline: toBylineInfo(byline),
2293
+ isNew
2294
+ };
2295
+ for (const hook of this.getTypedHooks("byline:afterSave")) await this.runLoggedHook("byline:afterSave", hook, (ctx) => hook.handler(event, ctx));
2296
+ }
2297
+ /** Run byline:afterDelete hooks. Errors are logged and never propagate. */
2298
+ async runBylineAfterDelete(byline) {
2299
+ const event = { byline: toBylineInfo(byline) };
2300
+ for (const hook of this.getTypedHooks("byline:afterDelete")) await this.runLoggedHook("byline:afterDelete", hook, (ctx) => hook.handler(event, ctx));
2301
+ }
2302
+ async runLoggedHook(name, hook, run) {
2303
+ try {
2304
+ await this.executeWithTimeout(() => run(this.getContext(hook.pluginId)), hook.timeout);
2305
+ } catch (error) {
2306
+ console.error(`[${name}] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2307
+ }
2308
+ }
2309
+ /**
2310
+ * Run page:metadata hooks. Each handler returns contributions that are
2311
+ * merged by the metadata collector. Errors are logged but don't propagate.
2312
+ */
2313
+ async runPageMetadata(event) {
2314
+ const hooks = this.getTypedHooks("page:metadata");
2315
+ const results = [];
2316
+ for (const hook of hooks) {
2317
+ const { handler } = hook;
2318
+ const ctx = this.getContext(hook.pluginId);
2319
+ try {
2320
+ const result = await this.executeWithTimeout(() => Promise.resolve(handler(event, ctx)), hook.timeout);
2321
+ if (result != null) {
2322
+ const contributions = Array.isArray(result) ? result : [result];
2323
+ results.push({
2324
+ pluginId: hook.pluginId,
2325
+ contributions
2326
+ });
2327
+ }
2328
+ } catch (error) {
2329
+ console.error(`[page:metadata] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2330
+ }
2331
+ }
2332
+ return results;
2333
+ }
2334
+ /**
2335
+ * Run page:fragments hooks. Only trusted plugins should be registered
2336
+ * for this hook. Errors are logged but don't propagate.
2337
+ */
2338
+ async runPageFragments(event) {
2339
+ const hooks = this.getTypedHooks("page:fragments");
2340
+ const results = [];
2341
+ for (const hook of hooks) {
2342
+ const { handler } = hook;
2343
+ const ctx = this.getContext(hook.pluginId);
2344
+ try {
2345
+ const result = await this.executeWithTimeout(() => Promise.resolve(handler(event, ctx)), hook.timeout);
2346
+ if (result != null) {
2347
+ const contributions = Array.isArray(result) ? result : [result];
2348
+ results.push({
2349
+ pluginId: hook.pluginId,
2350
+ contributions
2351
+ });
2352
+ }
2353
+ } catch (error) {
2354
+ console.error(`[page:fragments] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2355
+ }
2356
+ }
2357
+ return results;
2358
+ }
2359
+ /**
2360
+ * Check if any hooks are registered for a given name
2361
+ */
2362
+ hasHooks(name) {
2363
+ const hooks = this.hooks.get(name);
2364
+ return hooks !== void 0 && hooks.length > 0;
2365
+ }
2366
+ /**
2367
+ * Get hook count for debugging
2368
+ */
2369
+ getHookCount(name) {
2370
+ return this.hooks.get(name)?.length || 0;
2371
+ }
2372
+ /**
2373
+ * Get all registered hook names
2374
+ */
2375
+ getRegisteredHooks() {
2376
+ return [...this.hooks.keys()];
2377
+ }
2378
+ /**
2379
+ * Returns hook names where at least one handler declared exclusive: true
2380
+ */
2381
+ getRegisteredExclusiveHooks() {
2382
+ return [...this.exclusiveHookNames];
2383
+ }
2384
+ /**
2385
+ * Check if a hook is exclusive
2386
+ */
2387
+ isExclusiveHook(name) {
2388
+ return this.exclusiveHookNames.has(name);
2389
+ }
2390
+ /**
2391
+ * Set the selected provider for an exclusive hook.
2392
+ * Called by PluginManager after resolution.
2393
+ */
2394
+ setExclusiveSelection(hookName, pluginId) {
2395
+ this.exclusiveSelections.set(hookName, pluginId);
2396
+ }
2397
+ /**
2398
+ * Clear the selected provider for an exclusive hook.
2399
+ */
2400
+ clearExclusiveSelection(hookName) {
2401
+ this.exclusiveSelections.delete(hookName);
2402
+ }
2403
+ /**
2404
+ * Get the selected provider for an exclusive hook (if any).
2405
+ */
2406
+ getExclusiveSelection(hookName) {
2407
+ return this.exclusiveSelections.get(hookName);
2408
+ }
2409
+ /**
2410
+ * Get all plugins that registered a handler for a given exclusive hook.
2411
+ */
2412
+ getExclusiveHookProviders(hookName) {
2413
+ return (this.hooks.get(hookName) ?? []).filter((h) => h.exclusive).map((h) => ({ pluginId: h.pluginId }));
2414
+ }
2415
+ /**
2416
+ * Get all plugins that registered a non-exclusive handler for a given
2417
+ * hook (e.g. `email:beforeSend`, `email:afterSend`), preserving priority
2418
+ * order. Partitions with `getExclusiveHookProviders()`, which returns
2419
+ * plugins whose registration is marked `exclusive: true`.
2420
+ */
2421
+ getHookProviders(hookName) {
2422
+ return (this.hooks.get(hookName) ?? []).filter((h) => !h.exclusive).map((h) => ({ pluginId: h.pluginId }));
2423
+ }
2424
+ /**
2425
+ * Invoke an exclusive hook — dispatch only to the selected provider.
2426
+ * Returns null if no provider is selected or if the selected hook
2427
+ * is not found in the pipeline.
2428
+ *
2429
+ * This is a generic dispatch used by the email pipeline and other
2430
+ * exclusive hook consumers. The handler type is unknown — callers
2431
+ * must know the expected signature.
2432
+ *
2433
+ * Errors are isolated: a failing handler returns an error result
2434
+ * instead of propagating the exception to the caller.
2435
+ */
2436
+ async invokeExclusiveHook(hookName, event) {
2437
+ const selectedPluginId = this.exclusiveSelections.get(hookName);
2438
+ if (!selectedPluginId) return null;
2439
+ const hook = (this.hooks.get(hookName) ?? []).find((h) => h.pluginId === selectedPluginId && h.exclusive);
2440
+ if (!hook) return null;
2441
+ const start = Date.now();
2442
+ try {
2443
+ const ctx = this.getContext(selectedPluginId);
2444
+ const handler = hook.handler;
2445
+ return {
2446
+ result: await this.executeWithTimeout(() => handler(event, ctx), hook.timeout),
2447
+ pluginId: selectedPluginId,
2448
+ duration: Date.now() - start
2449
+ };
2450
+ } catch (error) {
2451
+ return {
2452
+ result: void 0,
2453
+ pluginId: selectedPluginId,
2454
+ error: error instanceof Error ? error : new Error(String(error)),
2455
+ duration: Date.now() - start
2456
+ };
2457
+ }
2458
+ }
2459
+ };
2460
+ /**
2461
+ * Create a hook pipeline from plugins
2462
+ */
2463
+ function createHookPipeline(plugins, factoryOptions) {
2464
+ return new HookPipeline(plugins, factoryOptions);
2465
+ }
2466
+ /** Options table key prefix for exclusive hook selections */
2467
+ const EXCLUSIVE_HOOK_KEY_PREFIX$1 = "emdash:exclusive_hook:";
2468
+ /**
2469
+ * Resolve exclusive hook selections.
2470
+ *
2471
+ * Shared algorithm used by both PluginManager and EmDashRuntime:
2472
+ * 1. If a DB selection exists and that plugin is active → keep it.
2473
+ * 2. If DB selection is stale (plugin inactive/gone) → clear it.
2474
+ * 3. If no selection and only one active provider → auto-select it. Fallback
2475
+ * providers are not counted when another provider is active, so a single
2476
+ * plugin provider is selected over a built-in fallback. A fallback
2477
+ * selection is kept in memory only.
2478
+ * 4. If preferred hints match an active provider → first match wins.
2479
+ * 5. If multiple providers and no hint → leave unselected (admin must choose).
2480
+ */
2481
+ async function resolveExclusiveHooks(opts) {
2482
+ const { pipeline, isActive, getOption, getOptions, setOption, deleteOption, preferredHints, fallbackProviders } = opts;
2483
+ const exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();
2484
+ if (exclusiveHookNames.length === 0) return;
2485
+ let batchedSelections;
2486
+ if (getOptions) try {
2487
+ batchedSelections = await getOptions(exclusiveHookNames.map((hookName) => `${EXCLUSIVE_HOOK_KEY_PREFIX$1}${hookName}`));
2488
+ } catch {
2489
+ return;
2490
+ }
2491
+ for (const hookName of exclusiveHookNames) {
2492
+ const providers = pipeline.getExclusiveHookProviders(hookName);
2493
+ const activeProviderIds = new Set(providers.map((p) => p.pluginId).filter((id) => isActive(id)));
2494
+ const key = `${EXCLUSIVE_HOOK_KEY_PREFIX$1}${hookName}`;
2495
+ let currentSelection = null;
2496
+ if (batchedSelections) currentSelection = batchedSelections.get(key) ?? null;
2497
+ else try {
2498
+ currentSelection = await getOption(key);
2499
+ } catch {
2500
+ continue;
2501
+ }
2502
+ if (currentSelection && activeProviderIds.has(currentSelection)) {
2503
+ pipeline.setExclusiveSelection(hookName, currentSelection);
2504
+ continue;
2505
+ }
2506
+ if (currentSelection) try {
2507
+ await deleteOption(key);
2508
+ } catch {}
2509
+ const candidates = activeProviderIds.size > 1 && fallbackProviders ? [...activeProviderIds].filter((id) => !fallbackProviders.has(id)) : [...activeProviderIds];
2510
+ if (candidates.length === 1) {
2511
+ const [onlyProvider] = candidates;
2512
+ if (!fallbackProviders?.has(onlyProvider)) try {
2513
+ await setOption(key, onlyProvider);
2514
+ } catch {}
2515
+ pipeline.setExclusiveSelection(hookName, onlyProvider);
2516
+ continue;
2517
+ }
2518
+ if (preferredHints) {
2519
+ let found = false;
2520
+ for (const [pluginId, hooks] of preferredHints) if (hooks.includes(hookName) && activeProviderIds.has(pluginId)) {
2521
+ try {
2522
+ await setOption(key, pluginId);
2523
+ } catch {}
2524
+ pipeline.setExclusiveSelection(hookName, pluginId);
2525
+ found = true;
2526
+ break;
2527
+ }
2528
+ if (found) continue;
2529
+ }
2530
+ pipeline.clearExclusiveSelection(hookName);
2531
+ }
2532
+ }
2533
+
2534
+ //#endregion
2535
+ //#region src/plugins/save-rejection.ts
2536
+ /**
2537
+ * Thrown by a `content:beforeSave` hook to reject a save with a message
2538
+ * that is safe to show to the editor. The runtime converts it into a
2539
+ * structured `SAVE_REJECTED` API error; any other exception thrown by the
2540
+ * hook cancels the save with a generic error that hides the exception
2541
+ * message.
2542
+ */
2543
+ var ContentSaveRejectedError = class extends Error {
2544
+ name = "ContentSaveRejectedError";
2545
+ };
2546
+ /**
2547
+ * Matches by name as well as by prototype: bundlers can duplicate this
2548
+ * module across SSR chunks, and an `instanceof` against the wrong copy of
2549
+ * the class would misreport a rejection as a plugin crash.
2550
+ */
2551
+ function isContentSaveRejection(error) {
2552
+ if (error instanceof ContentSaveRejectedError) return true;
2553
+ return error instanceof Error && error.name === "ContentSaveRejectedError";
2554
+ }
2555
+
2556
+ //#endregion
2557
+ //#region src/plugins/email.ts
2558
+ /**
2559
+ * Email Pipeline
2560
+ *
2561
+ * Orchestrates the three-stage email pipeline:
2562
+ * 1. email:beforeSend hooks (middleware — transform, validate, cancel)
2563
+ * 2. email:deliver hook (exclusive — exactly one provider delivers)
2564
+ * 3. email:afterSend hooks (logging, analytics, fire-and-forget)
2565
+ *
2566
+ * Security features:
2567
+ * - Recursion guard prevents re-entrant sends (e.g. plugin calling ctx.email.send from a hook)
2568
+ * - System emails (source="system") bypass email:beforeSend and email:afterSend hooks entirely
2569
+ * to protect auth tokens from exfiltration by plugin hooks
2570
+ *
2571
+ */
2572
+ /** Hook name for the exclusive email delivery hook */
2573
+ const EMAIL_DELIVER_HOOK = "email:deliver";
2574
+ /** Source value used for auth emails (magic links, invites, password resets) */
2575
+ const SYSTEM_SOURCE = "system";
2576
+ /**
2577
+ * Error thrown when ctx.email.send() is called but no provider is configured.
2578
+ */
2579
+ var EmailNotConfiguredError = class extends Error {
2580
+ constructor() {
2581
+ super("No email provider is configured. Install and activate an email provider plugin, then select it in Settings > Email.");
2582
+ this.name = "EmailNotConfiguredError";
2583
+ }
2584
+ };
2585
+ /**
2586
+ * Error thrown when a recursive email send is detected.
2587
+ */
2588
+ var EmailRecursionError = class extends Error {
2589
+ constructor() {
2590
+ super("Recursive email send detected. A plugin hook attempted to send an email from within the email pipeline, which would cause infinite recursion.");
2591
+ this.name = "EmailRecursionError";
2592
+ }
2593
+ };
2594
+ /**
2595
+ * Recursion guard using AsyncLocalStorage.
2596
+ *
2597
+ * EmailPipeline is a singleton (worker-lifetime cached via EmDashRuntime).
2598
+ * Instance state like `sendDepth` would false-positive under concurrent
2599
+ * requests because two unrelated sends would increment the same counter.
2600
+ * ALS scopes the guard to the current async execution context, so concurrent
2601
+ * requests each get their own independent recursion tracking.
2602
+ */
2603
+ const emailSendALS = new AsyncLocalStorage();
2604
+ /**
2605
+ * EmailPipeline orchestrates email delivery through the plugin hook system.
2606
+ *
2607
+ * The pipeline runs in three stages:
2608
+ * 1. email:beforeSend — middleware hooks that can transform or cancel messages
2609
+ * 2. email:deliver — exclusive hook dispatching to the selected provider
2610
+ * 3. email:afterSend — fire-and-forget hooks for logging/analytics
2611
+ */
2612
+ var EmailPipeline = class {
2613
+ pipeline;
2614
+ constructor(pipeline) {
2615
+ this.pipeline = pipeline;
2616
+ }
2617
+ /**
2618
+ * Replace the underlying hook pipeline.
2619
+ *
2620
+ * Called by the runtime when rebuilding the hook pipeline after a
2621
+ * plugin is enabled or disabled, so the email pipeline dispatches
2622
+ * to the current set of active hooks.
2623
+ */
2624
+ setPipeline(pipeline) {
2625
+ this.pipeline = pipeline;
2626
+ }
2627
+ /**
2628
+ * Send an email through the full pipeline.
2629
+ *
2630
+ * @param message - The email to send
2631
+ * @param source - Where the email originated ("system" for auth, plugin ID for plugins)
2632
+ * @throws EmailNotConfiguredError if no provider is selected
2633
+ * @throws EmailRecursionError if called re-entrantly from within a hook
2634
+ * @throws Error if the provider handler throws
2635
+ */
2636
+ async send(message, source) {
2637
+ const store = emailSendALS.getStore();
2638
+ if (store && store.depth > 0) throw new EmailRecursionError();
2639
+ const run = () => this.sendInner(message, source);
2640
+ if (store) {
2641
+ store.depth++;
2642
+ try {
2643
+ await run();
2644
+ } finally {
2645
+ store.depth--;
2646
+ }
2647
+ } else await emailSendALS.run({ depth: 1 }, run);
2648
+ }
2649
+ /**
2650
+ * Inner send implementation, separated from the recursion guard.
2651
+ */
2652
+ async sendInner(message, source) {
2653
+ if (!message || typeof message !== "object") throw new Error("Invalid email message: message must be an object");
2654
+ if (!message.to || typeof message.to !== "string") throw new Error("Invalid email message: 'to' is required and must be a string");
2655
+ if (message.cc !== void 0 && (!Array.isArray(message.cc) || message.cc.some((address) => typeof address !== "string"))) throw new Error("Invalid email message: 'cc' must be an array of strings");
2656
+ if (message.replyTo !== void 0 && typeof message.replyTo !== "string") throw new Error("Invalid email message: 'replyTo' must be a string");
2657
+ if (!message.subject || typeof message.subject !== "string") throw new Error("Invalid email message: 'subject' is required and must be a string");
2658
+ if (!message.text || typeof message.text !== "string") throw new Error("Invalid email message: 'text' is required and must be a string");
2659
+ const isSystemEmail = source === SYSTEM_SOURCE;
2660
+ let finalMessage;
2661
+ if (isSystemEmail) finalMessage = message;
2662
+ else {
2663
+ const beforeResult = await this.pipeline.runEmailBeforeSend(message, source);
2664
+ if (beforeResult.message === false) {
2665
+ const cancelledBy = beforeResult.results.find((r) => r.value === false)?.pluginId ?? "unknown";
2666
+ console.info(`[email] Email to "${message.to}" cancelled by plugin "${cancelledBy}"`);
2667
+ return;
2668
+ }
2669
+ finalMessage = beforeResult.message;
2670
+ }
2671
+ const deliverEvent = {
2672
+ message: finalMessage,
2673
+ source
2674
+ };
2675
+ const deliverResult = await this.pipeline.invokeExclusiveHook(EMAIL_DELIVER_HOOK, deliverEvent);
2676
+ if (!deliverResult) throw new EmailNotConfiguredError();
2677
+ if (deliverResult.error) {
2678
+ console.error(`[email:deliver] Provider "${deliverResult.pluginId}" failed to send email to "${finalMessage.to}":`, deliverResult.error);
2679
+ throw deliverResult.error;
2680
+ }
2681
+ if (!isSystemEmail) this.pipeline.runEmailAfterSend(finalMessage, source).catch((err) => console.error("[email] afterSend pipeline error:", err instanceof Error ? err.message : err));
2682
+ }
2683
+ /**
2684
+ * Check if an email provider is configured and available.
2685
+ *
2686
+ * Returns true if an email:deliver provider is selected in the exclusive
2687
+ * hook system. Plugins and auth code use this to decide whether to show
2688
+ * "send invite" vs "copy invite link" UI.
2689
+ */
2690
+ isAvailable() {
2691
+ return this.pipeline.getExclusiveSelection(EMAIL_DELIVER_HOOK) !== void 0;
2692
+ }
2693
+ };
2694
+
2695
+ //#endregion
2696
+ //#region src/plugins/manager.ts
2697
+ /** Options table key prefix for exclusive hook DB reads via PluginManager */
2698
+ const EXCLUSIVE_HOOK_KEY_PREFIX = "emdash:exclusive_hook:";
2699
+ /**
2700
+ * Plugin Manager v2
2701
+ *
2702
+ * Manages the full lifecycle of plugins and coordinates hooks/routes.
2703
+ */
2704
+ var PluginManager = class {
2705
+ plugins = /* @__PURE__ */ new Map();
2706
+ hookPipeline = null;
2707
+ routeRegistry = null;
2708
+ factoryOptions;
2709
+ initialized = false;
2710
+ constructor(options) {
2711
+ this.options = options;
2712
+ this.factoryOptions = {
2713
+ db: options.db,
2714
+ storage: options.storage,
2715
+ getUploadUrl: options.getUploadUrl,
2716
+ trustedProxyHeaders: options.trustedProxyHeaders
2717
+ };
2718
+ }
2719
+ /**
2720
+ * Set the email pipeline used when creating plugin contexts.
2721
+ * Reinitializes routes/hooks if already initialized so ctx.email is available immediately.
2722
+ */
2723
+ setEmailPipeline(pipeline) {
2724
+ this.factoryOptions.emailPipeline = pipeline;
2725
+ if (this.initialized) this.reinitialize();
2726
+ }
2727
+ /**
2728
+ * Register a plugin definition
2729
+ * This resolves the definition and adds it to the manager, but doesn't install it
2730
+ */
2731
+ register(definition) {
2732
+ const resolved = definePlugin(definition);
2733
+ if (this.plugins.has(resolved.id)) throw new Error(`Plugin "${resolved.id}" is already registered`);
2734
+ this.plugins.set(resolved.id, {
2735
+ plugin: resolved,
2736
+ state: "registered"
2737
+ });
2738
+ this.initialized = false;
2739
+ return resolved;
2740
+ }
2741
+ /**
2742
+ * Register multiple plugins
2743
+ */
2744
+ registerAll(definitions) {
2745
+ for (const def of definitions) this.register(def);
2746
+ }
2747
+ /**
2748
+ * Unregister a plugin
2749
+ * Plugin must be inactive or just registered
2750
+ */
2751
+ unregister(pluginId) {
2752
+ const entry = this.plugins.get(pluginId);
2753
+ if (!entry) return false;
2754
+ if (entry.state === "active") throw new Error(`Cannot unregister active plugin "${pluginId}". Deactivate it first.`);
2755
+ this.plugins.delete(pluginId);
2756
+ this.initialized = false;
2757
+ return true;
2758
+ }
2759
+ /**
2760
+ * Install a plugin (run install hooks, set up storage)
2761
+ */
2762
+ async install(pluginId) {
2763
+ const entry = this.plugins.get(pluginId);
2764
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2765
+ if (entry.state !== "registered") throw new Error(`Plugin "${pluginId}" is already installed (state: ${entry.state})`);
2766
+ this.ensureInitialized();
2767
+ const results = await this.hookPipeline.runPluginInstall(pluginId);
2768
+ const failed = results.find((r) => !r.success);
2769
+ if (failed) throw new Error(`Plugin install failed: ${failed.error?.message ?? "Unknown error"}`);
2770
+ entry.state = "installed";
2771
+ return results;
2772
+ }
2773
+ /**
2774
+ * Activate a plugin (run activate hooks, enable hooks/routes)
2775
+ */
2776
+ async activate(pluginId) {
2777
+ const entry = this.plugins.get(pluginId);
2778
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2779
+ if (entry.state === "active") return [];
2780
+ if (entry.state === "registered") await this.install(pluginId);
2781
+ this.ensureInitialized();
2782
+ const results = await this.hookPipeline.runPluginActivate(pluginId);
2783
+ const failed = results.find((r) => !r.success);
2784
+ if (failed) throw new Error(`Plugin activation failed: ${failed.error?.message ?? "Unknown error"}`);
2785
+ entry.state = "active";
2786
+ await setCronTasksEnabled(this.options.db, pluginId, true);
2787
+ this.reinitialize();
2788
+ await this.resolveExclusiveHooks();
2789
+ return results;
2790
+ }
2791
+ /**
2792
+ * Deactivate a plugin (run deactivate hooks, disable hooks/routes)
2793
+ */
2794
+ async deactivate(pluginId) {
2795
+ const entry = this.plugins.get(pluginId);
2796
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2797
+ if (entry.state !== "active") return [];
2798
+ this.ensureInitialized();
2799
+ const results = await this.hookPipeline.runPluginDeactivate(pluginId);
2800
+ await setCronTasksEnabled(this.options.db, pluginId, false);
2801
+ entry.state = "inactive";
2802
+ this.reinitialize();
2803
+ await this.resolveExclusiveHooks();
2804
+ return results;
2805
+ }
2806
+ /**
2807
+ * Uninstall a plugin (run uninstall hooks, optionally delete data)
2808
+ */
2809
+ async uninstall(pluginId, deleteData = false) {
2810
+ const entry = this.plugins.get(pluginId);
2811
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2812
+ if (entry.state === "active") await this.deactivate(pluginId);
2813
+ this.ensureInitialized();
2814
+ const results = await this.hookPipeline.runPluginUninstall(pluginId, deleteData);
2815
+ await this.deleteCronTasks(pluginId);
2816
+ this.plugins.delete(pluginId);
2817
+ this.initialized = false;
2818
+ await this.resolveExclusiveHooks();
2819
+ return results;
2820
+ }
2821
+ /**
2822
+ * Run content:beforeSave hooks across all active plugins
2823
+ */
2824
+ async runContentBeforeSave(content, collection, isNew, id, actor, details) {
2825
+ this.ensureInitialized();
2826
+ return this.hookPipeline.runContentBeforeSave(content, collection, isNew, id, actor, details);
2827
+ }
2828
+ /**
2829
+ * Run content:afterSave hooks across all active plugins
2830
+ */
2831
+ async runContentAfterSave(content, collection, isNew, actor, details) {
2832
+ this.ensureInitialized();
2833
+ return this.hookPipeline.runContentAfterSave(content, collection, isNew, actor, void 0, details);
2834
+ }
2835
+ /**
2836
+ * Run content:beforeDelete hooks across all active plugins
2837
+ */
2838
+ async runContentBeforeDelete(id, collection) {
2839
+ this.ensureInitialized();
2840
+ return this.hookPipeline.runContentBeforeDelete(id, collection);
2841
+ }
2842
+ /**
2843
+ * Run content:afterDelete hooks across all active plugins
2844
+ */
2845
+ async runContentAfterDelete(id, collection, permanent) {
2846
+ this.ensureInitialized();
2847
+ return this.hookPipeline.runContentAfterDelete(id, collection, permanent);
2848
+ }
2849
+ /**
2850
+ * Run content:afterPublish hooks across all active plugins
2851
+ */
2852
+ async runContentAfterPublish(content, collection) {
2853
+ this.ensureInitialized();
2854
+ return this.hookPipeline.runContentAfterPublish(content, collection);
2855
+ }
2856
+ /**
2857
+ * Run content:afterUnpublish hooks across all active plugins
2858
+ */
2859
+ async runContentAfterUnpublish(content, collection) {
2860
+ this.ensureInitialized();
2861
+ return this.hookPipeline.runContentAfterUnpublish(content, collection);
2862
+ }
2863
+ /**
2864
+ * Run content:afterRestore hooks across all active plugins
2865
+ */
2866
+ async runContentAfterRestore(content, collection) {
2867
+ this.ensureInitialized();
2868
+ return this.hookPipeline.runContentAfterRestore(content, collection);
2869
+ }
2870
+ /**
2871
+ * Run content:afterSchedule hooks across all active plugins
2872
+ */
2873
+ async runContentAfterSchedule(content, collection) {
2874
+ this.ensureInitialized();
2875
+ return this.hookPipeline.runContentAfterSchedule(content, collection);
2876
+ }
2877
+ /**
2878
+ * Run content:afterUnschedule hooks across all active plugins
2879
+ */
2880
+ async runContentAfterUnschedule(content, collection) {
2881
+ this.ensureInitialized();
2882
+ return this.hookPipeline.runContentAfterUnschedule(content, collection);
2883
+ }
2884
+ /**
2885
+ * Run media:beforeUpload hooks across all active plugins
2886
+ */
2887
+ async runMediaBeforeUpload(file) {
2888
+ this.ensureInitialized();
2889
+ return this.hookPipeline.runMediaBeforeUpload(file);
2890
+ }
2891
+ /**
2892
+ * Run media:afterUpload hooks across all active plugins
2893
+ */
2894
+ async runMediaAfterUpload(media) {
2895
+ this.ensureInitialized();
2896
+ return this.hookPipeline.runMediaAfterUpload(media);
2897
+ }
2898
+ /**
2899
+ * Invoke the cron hook for a specific plugin (per-plugin dispatch).
2900
+ * Used as the InvokeCronHookFn callback for CronExecutor.
2901
+ */
2902
+ async invokeCronHook(pluginId, event) {
2903
+ this.ensureInitialized();
2904
+ const result = await this.hookPipeline.invokeCronHook(pluginId, event);
2905
+ if (!result.success && result.error) throw result.error;
2906
+ }
2907
+ /**
2908
+ * Invoke a plugin route
2909
+ */
2910
+ async invokeRoute(pluginId, routeName, options) {
2911
+ this.ensureInitialized();
2912
+ return this.routeRegistry.invoke(pluginId, routeName, options);
2913
+ }
2914
+ /**
2915
+ * Get all routes for a plugin
2916
+ */
2917
+ getPluginRoutes(pluginId) {
2918
+ this.ensureInitialized();
2919
+ return this.routeRegistry.getRoutes(pluginId);
2920
+ }
2921
+ /**
2922
+ * Get a plugin by ID
2923
+ */
2924
+ getPlugin(pluginId) {
2925
+ return this.plugins.get(pluginId)?.plugin;
2926
+ }
2927
+ /**
2928
+ * Get plugin state
2929
+ */
2930
+ getPluginState(pluginId) {
2931
+ return this.plugins.get(pluginId)?.state;
2932
+ }
2933
+ /**
2934
+ * Get all registered plugins
2935
+ */
2936
+ getAllPlugins() {
2937
+ return Array.from(this.plugins.values(), (entry) => ({
2938
+ plugin: entry.plugin,
2939
+ state: entry.state
2940
+ }));
2941
+ }
2942
+ /**
2943
+ * Get all active plugins
2944
+ */
2945
+ getActivePlugins() {
2946
+ return [...this.plugins.values()].filter((entry) => entry.state === "active").map((entry) => entry.plugin);
2947
+ }
2948
+ /**
2949
+ * Check if a plugin exists
2950
+ */
2951
+ hasPlugin(pluginId) {
2952
+ return this.plugins.has(pluginId);
2953
+ }
2954
+ /**
2955
+ * Check if a plugin is active
2956
+ */
2957
+ isActive(pluginId) {
2958
+ return this.plugins.get(pluginId)?.state === "active";
2959
+ }
2960
+ /**
2961
+ * Get all plugins that registered a handler for an exclusive hook.
2962
+ */
2963
+ getExclusiveHookProviders(hookName) {
2964
+ this.ensureInitialized();
2965
+ return this.hookPipeline.getExclusiveHookProviders(hookName).map((p) => {
2966
+ const plugin = this.plugins.get(p.pluginId);
2967
+ return {
2968
+ pluginId: p.pluginId,
2969
+ pluginName: plugin?.plugin.id ?? p.pluginId
2970
+ };
2971
+ });
2972
+ }
2973
+ /**
2974
+ * Read the selected provider for an exclusive hook from the options table.
2975
+ */
2976
+ async getExclusiveHookSelection(hookName) {
2977
+ return new OptionsRepository(this.options.db).get(`${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`);
2978
+ }
2979
+ /**
2980
+ * Set the selected provider for an exclusive hook in the options table.
2981
+ * Pass null to clear the selection.
2982
+ */
2983
+ async setExclusiveHookSelection(hookName, pluginId) {
2984
+ const optionsRepo = new OptionsRepository(this.options.db);
2985
+ const key = `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`;
2986
+ if (pluginId === null) {
2987
+ await optionsRepo.delete(key);
2988
+ this.hookPipeline?.clearExclusiveSelection(hookName);
2989
+ return;
2990
+ }
2991
+ const entry = this.plugins.get(pluginId);
2992
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2993
+ if (entry.state !== "active") throw new Error(`Plugin "${pluginId}" is not active`);
2994
+ await optionsRepo.set(key, pluginId);
2995
+ this.hookPipeline?.setExclusiveSelection(hookName, pluginId);
2996
+ }
2997
+ /**
2998
+ * Resolution algorithm for exclusive hooks.
2999
+ *
3000
+ * Delegates to the shared resolveExclusiveHooks() function.
3001
+ * See hooks.ts for the full algorithm description.
3002
+ */
3003
+ async resolveExclusiveHooks(preferredHints) {
3004
+ this.ensureInitialized();
3005
+ const optionsRepo = new OptionsRepository(this.options.db);
3006
+ await resolveExclusiveHooks({
3007
+ pipeline: this.hookPipeline,
3008
+ isActive: (pluginId) => this.isActive(pluginId),
3009
+ getOption: (key) => optionsRepo.get(key),
3010
+ getOptions: (keys) => optionsRepo.getMany(keys),
3011
+ setOption: (key, value) => optionsRepo.set(key, value),
3012
+ deleteOption: async (key) => {
3013
+ await optionsRepo.delete(key);
3014
+ },
3015
+ preferredHints
3016
+ });
3017
+ }
3018
+ /**
3019
+ * Get all exclusive hooks with their providers and current selections.
3020
+ * Used by the admin API.
3021
+ */
3022
+ async getExclusiveHooksInfo() {
3023
+ this.ensureInitialized();
3024
+ const exclusiveHookNames = this.hookPipeline.getRegisteredExclusiveHooks();
3025
+ const result = [];
3026
+ for (const hookName of exclusiveHookNames) {
3027
+ const providers = this.hookPipeline.getExclusiveHookProviders(hookName);
3028
+ const selection = await this.getExclusiveHookSelection(hookName);
3029
+ result.push({
3030
+ hookName,
3031
+ providers,
3032
+ selectedPluginId: selection
3033
+ });
3034
+ }
3035
+ return result;
3036
+ }
3037
+ /**
3038
+ * Initialize or reinitialize the hook pipeline and route registry
3039
+ */
3040
+ ensureInitialized() {
3041
+ if (this.initialized) return;
3042
+ const activePlugins = this.getActivePlugins();
3043
+ this.hookPipeline = new HookPipeline(activePlugins, this.factoryOptions);
3044
+ this.routeRegistry = new PluginRouteRegistry(this.factoryOptions);
3045
+ for (const plugin of activePlugins) this.routeRegistry.register(plugin);
3046
+ this.initialized = true;
3047
+ }
3048
+ /**
3049
+ * Force reinitialization (useful after plugin state changes)
3050
+ */
3051
+ reinitialize() {
3052
+ this.initialized = false;
3053
+ this.ensureInitialized();
3054
+ }
3055
+ /**
3056
+ * Delete all cron tasks for a plugin.
3057
+ * Used during uninstall.
3058
+ */
3059
+ async deleteCronTasks(pluginId) {
3060
+ try {
3061
+ await sql`
3062
+ DELETE FROM _emdash_cron_tasks
3063
+ WHERE plugin_id = ${pluginId}
3064
+ `.execute(this.options.db);
3065
+ } catch {}
3066
+ }
3067
+ };
3068
+ /**
3069
+ * Create a plugin manager
3070
+ */
3071
+ function createPluginManager(options) {
3072
+ return new PluginManager(options);
3073
+ }
3074
+
3075
+ //#endregion
3076
+ //#region src/plugins/scheduler/node.ts
3077
+ /** Minimum polling interval (ms) — prevents tight loops if next_run_at is in the past */
3078
+ const MIN_INTERVAL_MS = 1e3;
3079
+ /**
3080
+ * Maximum polling interval (ms). Each wake runs the maintenance pass — stale
3081
+ * lock recovery *and* the scheduled-publishing sweep + system cleanup. The cap
3082
+ * is the worst-case latency for scheduled content when no plugin cron task is
3083
+ * due sooner (`getNextDueTime()` only knows about cron tasks, not content
3084
+ * `scheduled_at`). Held at 60s so Node publish latency matches the Cloudflare
3085
+ * Cron Trigger cadence (`* * * * *`) rather than lagging up to five minutes.
3086
+ */
3087
+ const MAX_INTERVAL_MS = 60 * 1e3;
3088
+ var NodeCronScheduler = class {
3089
+ timer = null;
3090
+ running = false;
3091
+ systemCleanup = null;
3092
+ constructor(executor) {
3093
+ this.executor = executor;
3094
+ }
3095
+ setSystemCleanup(fn) {
3096
+ this.systemCleanup = fn;
3097
+ }
3098
+ start() {
3099
+ this.running = true;
3100
+ this.arm();
3101
+ }
3102
+ stop() {
3103
+ this.running = false;
3104
+ if (this.timer) {
3105
+ clearTimeout(this.timer);
3106
+ this.timer = null;
3107
+ }
3108
+ }
3109
+ reschedule() {
3110
+ if (!this.running) return;
3111
+ if (this.timer) {
3112
+ clearTimeout(this.timer);
3113
+ this.timer = null;
3114
+ }
3115
+ this.arm();
3116
+ }
3117
+ arm() {
3118
+ if (!this.running) return;
3119
+ this.executor.getNextDueTime().then((nextDue) => {
3120
+ if (!this.running) return void 0;
3121
+ let delayMs;
3122
+ if (nextDue) {
3123
+ const dueAt = new Date(nextDue).getTime();
3124
+ delayMs = Math.max(dueAt - Date.now(), MIN_INTERVAL_MS);
3125
+ delayMs = Math.min(delayMs, MAX_INTERVAL_MS);
3126
+ } else delayMs = MAX_INTERVAL_MS;
3127
+ this.timer = setTimeout(() => {
3128
+ if (!this.running) return;
3129
+ this.executeTick();
3130
+ }, delayMs);
3131
+ if (this.timer && typeof this.timer === "object" && "unref" in this.timer) this.timer.unref();
3132
+ }).catch((error) => {
3133
+ console.error("[cron:node] Failed to get next due time:", error);
3134
+ if (this.running) {
3135
+ this.timer = setTimeout(() => this.arm(), MAX_INTERVAL_MS);
3136
+ if (this.timer && typeof this.timer === "object" && "unref" in this.timer) this.timer.unref();
3137
+ }
3138
+ });
3139
+ }
3140
+ executeTick() {
3141
+ if (!this.running) return;
3142
+ const tasks = [this.executor.tick(), this.executor.recoverStaleLocks()];
3143
+ if (this.systemCleanup) tasks.push(this.systemCleanup());
3144
+ Promise.allSettled(tasks).then((results) => {
3145
+ for (const r of results) if (r.status === "rejected") console.error("[cron:node] Tick task failed:", r.reason);
3146
+ }).finally(() => {
3147
+ if (this.running) this.arm();
3148
+ });
3149
+ }
3150
+ };
3151
+
3152
+ //#endregion
3153
+ //#region src/plugins/sandbox/noop.ts
3154
+ /**
3155
+ * Error thrown when attempting to use sandboxing on an unsupported platform.
3156
+ */
3157
+ var SandboxNotAvailableError = class extends Error {
3158
+ constructor() {
3159
+ super("Plugin sandboxing is not available. Configure a sandbox runner: use @emdash-cms/cloudflare/sandbox on Cloudflare, or @emdash-cms/sandbox-workerd/sandbox on Node.js (requires workerd). Without sandboxing, use trusted plugins (from config) instead.");
3160
+ this.name = "SandboxNotAvailableError";
3161
+ }
3162
+ };
3163
+ /**
3164
+ * No-op sandbox runner for platforms without isolation support.
3165
+ *
3166
+ * - `isAvailable()` returns false
3167
+ * - `load()` throws SandboxNotAvailableError
3168
+ * - `terminateAll()` is a no-op
3169
+ *
3170
+ * This is the default runner when no platform adapter is configured.
3171
+ */
3172
+ var NoopSandboxRunner = class {
3173
+ /**
3174
+ * Always returns false - sandboxing is not available.
3175
+ */
3176
+ isAvailable() {
3177
+ return false;
3178
+ }
3179
+ /**
3180
+ * Always returns false - no sandbox runtime to be healthy.
3181
+ */
3182
+ isHealthy() {
3183
+ return false;
3184
+ }
3185
+ /**
3186
+ * Always throws - can't load sandboxed plugins without isolation.
3187
+ */
3188
+ async load(_manifest, _code) {
3189
+ throw new SandboxNotAvailableError();
3190
+ }
3191
+ /**
3192
+ * No-op - sandboxing not available, email callback is irrelevant.
3193
+ */
3194
+ setEmailSend() {}
3195
+ /**
3196
+ * No-op - nothing to terminate.
3197
+ */
3198
+ async terminateAll() {}
3199
+ };
3200
+ /**
3201
+ * Create a no-op sandbox runner.
3202
+ * This is used as the default when no platform adapter is configured.
3203
+ */
3204
+ function createNoopSandboxRunner(_options) {
3205
+ return new NoopSandboxRunner();
3206
+ }
3207
+
3208
+ //#endregion
3209
+ //#region src/plugins/sandbox/hook-result.ts
3210
+ /** Current wire-format version for sandbox hook result envelopes. */
3211
+ const SANDBOX_HOOK_RESULT_VERSION = 1;
3212
+ /** Maximum editor-facing rejection reason accepted from a sandboxed plugin. */
3213
+ const MAX_SANDBOX_SAVE_REJECTION_REASON_LENGTH = 500;
3214
+ function isRecord(value) {
3215
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3216
+ }
3217
+ /** Validate and normalize an untrusted value returned across the sandbox boundary. */
3218
+ function inspectSandboxHookResult(value) {
3219
+ if (!isRecord(value) || !Object.hasOwn(value, "__emdashSandboxHookResult")) return { kind: "value" };
3220
+ if (value.__emdashSandboxHookResult !== true || value.version !== SANDBOX_HOOK_RESULT_VERSION || !isRecord(value.error) || value.error.code !== "SAVE_REJECTED" || typeof value.error.reason !== "string") return { kind: "malformed" };
3221
+ const reason = value.error.reason.trim();
3222
+ if (reason.length === 0 || reason.length > MAX_SANDBOX_SAVE_REJECTION_REASON_LENGTH) return { kind: "malformed" };
3223
+ return {
3224
+ kind: "error",
3225
+ error: {
3226
+ code: "SAVE_REJECTED",
3227
+ reason
3228
+ }
3229
+ };
3230
+ }
3231
+
3232
+ //#endregion
3233
+ //#region src/comments/ranking.ts
3234
+ /**
3235
+ * Comment ranking utilities (Tier 1 of the best-in-class comments RFC).
3236
+ *
3237
+ * Wilson score lower-bound (95% confidence) — the same primitive Reddit uses
3238
+ * for its "Best" comment sort. Ranks by the statistical lower bound of the
3239
+ * positive-reaction proportion rather than the raw count, so a comment with a
3240
+ * couple of reactions can't outrank a heavily-reacted one until it earns
3241
+ * confidence, and a late-but-popular comment still rises (submission time is
3242
+ * irrelevant).
3243
+ *
3244
+ * Positive-only reactions (the recommended default) degrade gracefully: with
3245
+ * `down = 0` the score still increases monotonically with `up` while penalising
3246
+ * low-sample comments — wilson(1,0) ≈ 0.21, wilson(10,0) ≈ 0.73,
3247
+ * wilson(200,0) ≈ 0.98.
3248
+ */
3249
+ /** z for a 95% two-sided confidence interval. */
3250
+ const DEFAULT_Z = 1.96;
3251
+ /** Reactions that count against a comment when both signals are in use. */
3252
+ const NEGATIVE_REACTIONS = new Set(["dislike", "down"]);
3253
+ /**
3254
+ * Wilson score lower bound of the positive proportion.
3255
+ *
3256
+ * @param up count of positive reactions
3257
+ * @param down count of negative reactions
3258
+ * @returns a score in [0, 1]; 0 when there are no reactions
3259
+ */
3260
+ function wilsonLowerBound(up, down, z = DEFAULT_Z) {
3261
+ const n = up + down;
3262
+ if (n <= 0) return 0;
3263
+ const phat = up / n;
3264
+ const z2 = z * z;
3265
+ const denom = 1 + z2 / n;
3266
+ return (phat + z2 / (2 * n) - z * Math.sqrt((phat * (1 - phat) + z2 / (4 * n)) / n)) / denom;
3267
+ }
3268
+ /**
3269
+ * Reduce a per-reaction count map to a single rank score via the Wilson
3270
+ * lower bound. Any reaction not in {@link NEGATIVE_REACTIONS} is treated as
3271
+ * positive, so the positive-only default (just `like`) works without special
3272
+ * casing.
3273
+ */
3274
+ function reactionScore(counts) {
3275
+ let up = 0;
3276
+ let down = 0;
3277
+ for (const [reaction, count] of Object.entries(counts)) if (NEGATIVE_REACTIONS.has(reaction)) down += count;
3278
+ else up += count;
3279
+ return wilsonLowerBound(up, down);
3280
+ }
3281
+
3282
+ //#endregion
3283
+ //#region src/comments/query.ts
3284
+ /**
3285
+ * Get approved comments for a content item.
3286
+ *
3287
+ * @example
3288
+ * ```ts
3289
+ * import { getComments } from "emdash";
3290
+ *
3291
+ * const { items, total } = await getComments({
3292
+ * collection: "posts",
3293
+ * contentId: post.id,
3294
+ * threaded: true,
3295
+ * });
3296
+ * ```
3297
+ */
3298
+ async function getComments(options) {
3299
+ const sort = options.sort ?? "oldest";
3300
+ const withReactions = options.reactions || sort === "best";
3301
+ const threaded = options.threaded ? "t" : "f";
3302
+ return cachedQuery({
3303
+ namespace: CacheNamespace.COMMENTS,
3304
+ key: `comments:${options.collection}:${options.contentId}:${threaded}:${withReactions ? "r" : "n"}:${sort}`,
3305
+ load: async () => {
3306
+ return getCommentsWithDb(await getDb(), options);
3307
+ }
3308
+ });
3309
+ }
3310
+ /**
3311
+ * Get approved comments with an explicit db handle.
3312
+ *
3313
+ * @internal Use `getComments()` in templates. This variant is for routes
3314
+ * that already have a database handle.
3315
+ */
3316
+ async function getCommentsWithDb(db, options) {
3317
+ const repo = new CommentRepository(db);
3318
+ const total = await repo.countByContent(options.collection, options.contentId, "approved");
3319
+ const result = await repo.findByContent(options.collection, options.contentId, {
3320
+ status: "approved",
3321
+ limit: 500
3322
+ });
3323
+ const items = options.threaded ? CommentRepository.assembleThreads(result.items).map((c) => CommentRepository.toPublicComment(c)) : result.items.map((c) => CommentRepository.toPublicComment(c));
3324
+ if (options.reactions || options.sort === "best") {
3325
+ await attachReactions(db, items);
3326
+ if (options.sort === "best") sortByBest(items);
3327
+ }
3328
+ return {
3329
+ items,
3330
+ total
3331
+ };
3332
+ }
3333
+ /**
3334
+ * Attach aggregate reaction counts to a list of public comments (and their
3335
+ * replies), in a single batched query.
3336
+ */
3337
+ async function attachReactions(db, items) {
3338
+ const ids = [];
3339
+ for (const comment of items) {
3340
+ ids.push(comment.id);
3341
+ if (comment.replies) for (const reply of comment.replies) ids.push(reply.id);
3342
+ }
3343
+ if (ids.length === 0) return;
3344
+ const counts = await new CommentReactionRepository(db).countsForComments(ids);
3345
+ const assign = (comment) => {
3346
+ const reactions = counts.get(comment.id);
3347
+ if (reactions) comment.reactions = reactions;
3348
+ };
3349
+ for (const comment of items) {
3350
+ assign(comment);
3351
+ comment.replies?.forEach(assign);
3352
+ }
3353
+ }
3354
+ /**
3355
+ * Sort top-level comments by Wilson-scored reactions (descending), tie-broken
3356
+ * by oldest-first to keep ordering stable.
3357
+ */
3358
+ function sortByBest(items) {
3359
+ items.sort((a, b) => {
3360
+ const scoreDelta = reactionScore(b.reactions ?? {}) - reactionScore(a.reactions ?? {});
3361
+ if (scoreDelta !== 0) return scoreDelta;
3362
+ if (a.createdAt < b.createdAt) return -1;
3363
+ if (a.createdAt > b.createdAt) return 1;
3364
+ return 0;
3365
+ });
3366
+ }
3367
+ /**
3368
+ * Get the count of approved comments for a content item.
3369
+ *
3370
+ * @example
3371
+ * ```ts
3372
+ * import { getCommentCount } from "emdash";
3373
+ *
3374
+ * const count = await getCommentCount("posts", post.id);
3375
+ * ```
3376
+ */
3377
+ async function getCommentCount(collection, contentId) {
3378
+ return getCommentCountWithDb(await getDb(), collection, contentId);
3379
+ }
3380
+ /**
3381
+ * Get comment count with an explicit db handle.
3382
+ *
3383
+ * @internal Use `getCommentCount()` in templates.
3384
+ */
3385
+ async function getCommentCountWithDb(db, collection, contentId) {
3386
+ return new CommentRepository(db).countByContent(collection, contentId, "approved");
3387
+ }
3388
+
3389
+ //#endregion
3390
+ //#region src/menus/index.ts
3391
+ /**
3392
+ * Get a menu by name with resolved URLs.
3393
+ *
3394
+ * @example
3395
+ * ```ts
3396
+ * const menu = await getMenu("primary");
3397
+ * const menuEs = await getMenu("primary", { locale: "es" });
3398
+ * ```
3399
+ */
3400
+ async function getMenu(name, options = {}) {
3401
+ const locale = resolveLocale(options.locale);
3402
+ const trailingSlash = options.trailingSlash ?? await getHostTrailingSlash();
3403
+ return requestCached(`menu:${name}:${locale ?? "*"}:${trailingSlash}`, () => cachedQuery({
3404
+ namespace: CacheNamespace.MENUS,
3405
+ key: `${name}:${locale ?? "*"}:${trailingSlash}`,
3406
+ load: async () => {
3407
+ return getMenuWithDb(name, await getDb(), {
3408
+ locale,
3409
+ trailingSlash
3410
+ });
3411
+ }
3412
+ }));
3413
+ }
3414
+ async function getHostTrailingSlash() {
3415
+ try {
3416
+ return (await import("virtual:emdash/config")).default?.trailingSlash ?? "ignore";
3417
+ } catch {
3418
+ return "ignore";
3419
+ }
3420
+ }
3421
+ /**
3422
+ * Get menu by name with resolved URLs (with explicit db). Internal helper for
3423
+ * admin routes that already have a database handle.
3424
+ */
3425
+ async function getMenuWithDb(name, db, options = {}) {
3426
+ const chain = resolveLocaleChain(options.locale);
3427
+ let query = db.selectFrom("_emdash_menus as m").leftJoin("_emdash_menu_items as i", "i.menu_id", "m.id").selectAll("i").select([
3428
+ "m.id as m_id",
3429
+ "m.name as m_name",
3430
+ "m.label as m_label",
3431
+ "m.locale as m_locale",
3432
+ "m.translation_group as m_translation_group"
3433
+ ]).where("m.name", "=", name).orderBy("i.sort_order", "asc");
3434
+ if (chain.length > 0) query = query.where("m.locale", "in", chain);
3435
+ const rows = await query.execute();
3436
+ const locales = new Set(rows.map((row) => row.m_locale));
3437
+ const locale = chain.length === 0 ? [...locales].toSorted()[0] : chain.find((l) => locales.has(l));
3438
+ const menuRow = rows.find((row) => row.m_locale === locale);
3439
+ if (!menuRow) return null;
3440
+ const menuId = menuRow.m_id;
3441
+ const items = await buildMenuTree(rows.filter((row) => row.m_id === menuId && row.id !== null), db, menuRow.m_locale, options.trailingSlash);
3442
+ return {
3443
+ id: menuRow.m_id,
3444
+ name: menuRow.m_name,
3445
+ label: menuRow.m_label,
3446
+ items,
3447
+ locale: menuRow.m_locale,
3448
+ translationGroup: menuRow.m_translation_group
3449
+ };
3450
+ }
3451
+ /**
3452
+ * Get all menus (without items, locale-filtered — for admin list / site nav
3453
+ * summaries). When no locale is configured, returns menus across all locales.
3454
+ */
3455
+ async function getMenus(options = {}) {
3456
+ return getMenusWithDb(await getDb(), options);
3457
+ }
3458
+ /**
3459
+ * Get all menus (with explicit db)
3460
+ *
3461
+ * @internal Use `getMenus()` in templates. This variant is for admin routes
3462
+ * that already have a database handle.
3463
+ */
3464
+ async function getMenusWithDb(db, options = {}) {
3465
+ const locale = resolveLocale(options.locale);
3466
+ let query = db.selectFrom("_emdash_menus").select([
3467
+ "id",
3468
+ "name",
3469
+ "label",
3470
+ "locale"
3471
+ ]).orderBy("name", "asc");
3472
+ if (locale !== void 0) query = query.where("locale", "=", locale);
3473
+ return query.execute();
3474
+ }
3475
+ /**
3476
+ * Get a menu by name with a Workers edge-cache hint.
3477
+ *
3478
+ * Use the returned `cacheHint` with `Astro.cache.set()` so pages that render
3479
+ * this menu can be purged automatically when the menu is edited.
3480
+ */
3481
+ async function getMenuWithCacheHint(name, options = {}) {
3482
+ return {
3483
+ data: await getMenu(name, options),
3484
+ cacheHint: { tags: [menuTag(name)] }
3485
+ };
3486
+ }
3487
+ /**
3488
+ * Build a hierarchical menu tree from a flat list of items. Items are
3489
+ * resolved against the given `locale` so references land on the right
3490
+ * per-locale content rows.
3491
+ */
3492
+ async function buildMenuTree(items, db, locale, trailingSlash) {
3493
+ const contentReferences = collectContentReferences(items);
3494
+ const taxonomyReferences = new Set(items.flatMap((item) => item.type === "taxonomy" && item.reference_id ? [item.reference_id] : []));
3495
+ const [urlPatterns, contentLookup, taxonomyLookup] = await Promise.all([
3496
+ contentReferences.size > 0 ? getCollectionUrlPatterns(db, new Set(contentReferences.keys())) : /* @__PURE__ */ new Map(),
3497
+ resolveContentReferences(db, contentReferences, locale),
3498
+ resolveTaxonomyReferences(db, taxonomyReferences, locale)
3499
+ ]);
3500
+ const validItems = (await Promise.all(items.map((item) => resolveMenuItem(item, urlPatterns, contentLookup, taxonomyLookup, locale, trailingSlash)))).filter((item) => item !== null);
3501
+ const itemMap = /* @__PURE__ */ new Map();
3502
+ const rootItems = [];
3503
+ for (const item of validItems) itemMap.set(item.id, {
3504
+ ...item,
3505
+ children: []
3506
+ });
3507
+ for (const item of items) {
3508
+ const menuItem = itemMap.get(item.id);
3509
+ if (!menuItem) continue;
3510
+ if (item.parent_id) {
3511
+ const parent = itemMap.get(item.parent_id);
3512
+ if (parent) parent.children.push(menuItem);
3513
+ else rootItems.push(menuItem);
3514
+ } else rootItems.push(menuItem);
3515
+ }
3516
+ return rootItems;
3517
+ }
3518
+ function collectContentReferences(items) {
3519
+ const references = /* @__PURE__ */ new Map();
3520
+ for (const item of items) {
3521
+ const reference = getContentReference(item);
3522
+ if (!reference) continue;
3523
+ let ids = references.get(reference.collection);
3524
+ if (!ids) {
3525
+ ids = /* @__PURE__ */ new Set();
3526
+ references.set(reference.collection, ids);
3527
+ }
3528
+ ids.add(reference.id);
3529
+ }
3530
+ return references;
3531
+ }
3532
+ function getContentReference(item) {
3533
+ if (item.type === "page" || item.type === "post") {
3534
+ if (!item.reference_id) return null;
3535
+ return {
3536
+ collection: item.reference_collection || `${item.type}s`,
3537
+ id: item.reference_id
3538
+ };
3539
+ }
3540
+ if (item.type === "collection") {
3541
+ if (!item.reference_collection || !item.reference_id) return null;
3542
+ return {
3543
+ collection: item.reference_collection,
3544
+ id: item.reference_id
3545
+ };
3546
+ }
3547
+ if (item.type !== "custom" && item.type !== "taxonomy" && item.reference_collection && item.reference_id) return {
3548
+ collection: item.reference_collection,
3549
+ id: item.reference_id
3550
+ };
3551
+ return null;
3552
+ }
3553
+ /**
3554
+ * Look up the `url_pattern` for a set of collection slugs, request-cached so
3555
+ * a page rendering several menus (header, footer, ...) only pays for the
3556
+ * lookup once per distinct slug set. Callers must treat the returned map as
3557
+ * read-only — it is shared across cache hits within the request.
3558
+ */
3559
+ function getCollectionUrlPatterns(db, collectionSlugs) {
3560
+ return requestCached(`menu-collection-patterns:${[...collectionSlugs].toSorted().join(",")}`, async () => {
3561
+ const rows = await db.selectFrom("_emdash_collections").select(["slug", "url_pattern"]).where("slug", "in", [...collectionSlugs]).execute();
3562
+ const urlPatterns = /* @__PURE__ */ new Map();
3563
+ for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
3564
+ return urlPatterns;
3565
+ });
3566
+ }
3567
+ /**
3568
+ * Resolve a single menu item's URL. `reference_id` is a translation_group
3569
+ * (migration 036 remapped all existing references); we look it up against
3570
+ * the per-locale ec_* row or per-locale taxonomy row.
3571
+ */
3572
+ async function resolveMenuItem(item, urlPatterns, contentLookup, taxonomyLookup, locale, trailingSlash) {
3573
+ let url;
3574
+ switch (item.type) {
3575
+ case "custom":
3576
+ url = item.custom_url || "#";
3577
+ break;
3578
+ case "page":
3579
+ case "post":
3580
+ url = await resolveContentUrl(item.reference_collection || `${item.type}s`, item.reference_id, urlPatterns, contentLookup, locale, trailingSlash);
3581
+ if (url === null) return null;
3582
+ break;
3583
+ case "taxonomy":
3584
+ url = resolveTaxonomyUrl(item.reference_id, taxonomyLookup);
3585
+ if (url === null) return null;
3586
+ break;
3587
+ case "collection":
3588
+ if (!item.reference_collection) return null;
3589
+ if (item.reference_id) {
3590
+ url = await resolveContentUrl(item.reference_collection, item.reference_id, urlPatterns, contentLookup, locale, trailingSlash);
3591
+ if (url === null) return null;
3592
+ } else url = `/${item.reference_collection}/`;
3593
+ break;
3594
+ default: if (item.reference_collection && item.reference_id) {
3595
+ url = await resolveContentUrl(item.reference_collection, item.reference_id, urlPatterns, contentLookup, locale, trailingSlash);
3596
+ if (url === null) return null;
3597
+ } else url = "#";
3598
+ }
3599
+ return {
3600
+ id: item.id,
3601
+ label: item.label,
3602
+ url: sanitizeHref(url),
3603
+ target: item.target || void 0,
3604
+ titleAttr: item.title_attr || void 0,
3605
+ cssClasses: item.css_classes || void 0,
3606
+ children: []
3607
+ };
3608
+ }
3609
+ async function resolveContentReferences(db, references, locale) {
3610
+ const entries = await Promise.all(Array.from(references, async ([collection, referenceGroups]) => {
3611
+ const lookup = /* @__PURE__ */ new Map();
3612
+ const localized = /* @__PURE__ */ new Map();
3613
+ try {
3614
+ validateIdentifier(collection, "menu item collection");
3615
+ for (const batch of chunks([...referenceGroups], SQL_BATCH_SIZE)) {
3616
+ const result = await sql`
3617
+ SELECT id, slug, published_at, locale, translation_group
3618
+ FROM ${sql.ref(`ec_${collection}`)}
3619
+ WHERE translation_group IN (${sql.join(batch)})
3620
+ `.execute(db);
3621
+ for (const row of result.rows) if (shouldPreferLocalizedRow(row, localized.get(row.translation_group), locale)) localized.set(row.translation_group, row);
3622
+ }
3623
+ for (const [referenceGroup, row] of localized) lookup.set(referenceGroup, {
3624
+ id: row.id,
3625
+ slug: row.slug,
3626
+ publishedAt: row.published_at
3627
+ });
3628
+ const unresolved = [...referenceGroups].filter((id) => !lookup.has(id));
3629
+ for (const batch of chunks(unresolved, SQL_BATCH_SIZE)) {
3630
+ const result = await sql`
3631
+ SELECT id, slug, published_at FROM ${sql.ref(`ec_${collection}`)}
3632
+ WHERE id IN (${sql.join(batch)})
3633
+ `.execute(db);
3634
+ for (const row of result.rows) lookup.set(row.id, {
3635
+ id: row.id,
3636
+ slug: row.slug,
3637
+ publishedAt: row.published_at
3638
+ });
3639
+ }
3640
+ } catch (error) {
3641
+ console.error(`Failed to resolve content URLs for ${collection}:`, error);
3642
+ }
3643
+ return [collection, lookup];
3644
+ }));
3645
+ return new Map(entries);
3646
+ }
3647
+ function shouldPreferLocalizedRow(candidate, existing, locale) {
3648
+ if (!existing) return true;
3649
+ if (candidate.locale === locale) return existing.locale !== locale || candidate.id < existing.id;
3650
+ if (existing.locale === locale) return false;
3651
+ return candidate.locale < existing.locale || candidate.locale === existing.locale && candidate.id < existing.id;
3652
+ }
3653
+ /**
3654
+ * Resolve the URL for a content reference. `referenceGroup` is the content
3655
+ * row's translation_group; we look up the row in the requested locale
3656
+ * (falling back to the source if no translation exists so the menu link is
3657
+ * still clickable).
3658
+ */
3659
+ async function resolveContentUrl(collection, referenceGroup, urlPatterns, contentLookup, locale, trailingSlash) {
3660
+ if (!referenceGroup) return null;
3661
+ const row = contentLookup.get(collection)?.get(referenceGroup);
3662
+ if (!row) return null;
3663
+ return resolveLocalizedContentRoutePath({
3664
+ pattern: urlPatterns.get(collection) ?? null,
3665
+ collection,
3666
+ slug: row.slug,
3667
+ id: row.id,
3668
+ date: row.publishedAt,
3669
+ locale,
3670
+ trailingSlash
3671
+ });
3672
+ }
3673
+ async function resolveTaxonomyReferences(db, referenceGroups, locale) {
3674
+ const lookup = /* @__PURE__ */ new Map();
3675
+ const localized = /* @__PURE__ */ new Map();
3676
+ try {
3677
+ for (const batch of chunks([...referenceGroups], SQL_BATCH_SIZE)) {
3678
+ const rows = await db.selectFrom("taxonomies").select([
3679
+ "id",
3680
+ "name",
3681
+ "slug",
3682
+ "locale",
3683
+ "translation_group"
3684
+ ]).where("translation_group", "in", batch).$narrowType().execute();
3685
+ for (const row of rows) if (shouldPreferLocalizedRow(row, localized.get(row.translation_group), locale)) localized.set(row.translation_group, row);
3686
+ }
3687
+ for (const [referenceGroup, row] of localized) lookup.set(referenceGroup, {
3688
+ name: row.name,
3689
+ slug: row.slug
3690
+ });
3691
+ const unresolved = [...referenceGroups].filter((id) => !lookup.has(id));
3692
+ for (const batch of chunks(unresolved, SQL_BATCH_SIZE)) {
3693
+ const rows = await db.selectFrom("taxonomies").select([
3694
+ "id",
3695
+ "name",
3696
+ "slug"
3697
+ ]).where("id", "in", batch).execute();
3698
+ for (const row of rows) lookup.set(row.id, {
3699
+ name: row.name,
3700
+ slug: row.slug
3701
+ });
3702
+ }
3703
+ } catch (error) {
3704
+ console.error("Failed to resolve taxonomy URLs:", error);
3705
+ }
3706
+ return lookup;
3707
+ }
3708
+ /**
3709
+ * Resolve URL for a taxonomy term reference. `referenceGroup` is the term's
3710
+ * translation_group; we pick the row in the active locale (or fall back).
3711
+ */
3712
+ function resolveTaxonomyUrl(referenceGroup, taxonomyLookup) {
3713
+ if (!referenceGroup) return null;
3714
+ const taxonomy = taxonomyLookup.get(referenceGroup);
3715
+ if (!taxonomy) return null;
3716
+ return `/${taxonomy.name}/${taxonomy.slug}`;
3717
+ }
3718
+
3719
+ //#endregion
3720
+ export { file as A, definePlugin as C, portableTextIdentityExtensions as D, prosemirrorToPortableText as E, EmDashConfigurationError as M, portableText as O, resolveExclusiveHooks as S, portableTextToProsemirror as T, isContentSaveRejection as _, getComments as a, createHookPipeline as b, inspectSandboxHookResult as c, createNoopSandboxRunner as d, NodeCronScheduler as f, ContentSaveRejectedError as g, EmailPipeline as h, getCommentCount as i, image as j, reference as k, NoopSandboxRunner as l, createPluginManager as m, getMenuWithCacheHint as n, MAX_SANDBOX_SAVE_REJECTION_REASON_LENGTH as o, PluginManager as p, getMenus as r, SANDBOX_HOOK_RESULT_VERSION as s, getMenu as t, SandboxNotAvailableError as u, EXCLUSIVE_HOOK_KEY_PREFIX$1 as v, definePluginRoute as w, getActiveContentSaveHookName as x, HookPipeline as y };
3721
+ //# sourceMappingURL=menus-DL8IPIcm.mjs.map