emdash 0.37.0 → 0.39.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 (1445) hide show
  1. package/dist/action-token-On1sot_c.mjs +26 -0
  2. package/dist/action-token-On1sot_c.mjs.map +1 -0
  3. package/dist/activation-CaMfdBt1.mjs +382 -0
  4. package/dist/activation-CaMfdBt1.mjs.map +1 -0
  5. package/dist/adapters-Cq10K4_c.d.mts +171 -0
  6. package/dist/adapters-Cq10K4_c.d.mts.map +1 -0
  7. package/dist/after-PcEvMY_6.mjs +96 -0
  8. package/dist/after-PcEvMY_6.mjs.map +1 -0
  9. package/dist/allowed-origins-1Sa-2wZK.mjs +116 -0
  10. package/dist/allowed-origins-1Sa-2wZK.mjs.map +1 -0
  11. package/dist/api/route-utils.d.mts +4 -3
  12. package/dist/api/route-utils.d.mts.map +1 -1
  13. package/dist/api/route-utils.mjs +45 -29
  14. package/dist/api/route-utils.mjs.map +1 -1
  15. package/dist/api/schemas/index.d.mts +2 -2
  16. package/dist/api/schemas/index.mjs +7 -6
  17. package/dist/api-DmjKxPCY.mjs +4831 -0
  18. package/dist/api-DmjKxPCY.mjs.map +1 -0
  19. package/dist/api-tokens-BIXItFKV.mjs +163 -0
  20. package/dist/api-tokens-BIXItFKV.mjs.map +1 -0
  21. package/dist/apply-drpfmqrR.mjs +1005 -0
  22. package/dist/apply-drpfmqrR.mjs.map +1 -0
  23. package/dist/artifact-fetch-COiMhVgk.mjs +813 -0
  24. package/dist/artifact-fetch-COiMhVgk.mjs.map +1 -0
  25. package/dist/astro/index.d.mts +14 -12
  26. package/dist/astro/index.d.mts.map +1 -1
  27. package/dist/astro/index.mjs +123 -13
  28. package/dist/astro/index.mjs.map +1 -1
  29. package/dist/astro/middleware/auth.d.mts +12 -10
  30. package/dist/astro/middleware/auth.d.mts.map +1 -1
  31. package/dist/astro/middleware/auth.mjs +20 -11
  32. package/dist/astro/middleware/auth.mjs.map +1 -1
  33. package/dist/astro/middleware/media-usage-write-fence.mjs +5 -5
  34. package/dist/astro/middleware/redirect.mjs +11 -9
  35. package/dist/astro/middleware/redirect.mjs.map +1 -1
  36. package/dist/astro/middleware/request-context.d.mts +1 -1
  37. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  38. package/dist/astro/middleware/request-context.mjs +154 -28
  39. package/dist/astro/middleware/request-context.mjs.map +1 -1
  40. package/dist/astro/middleware/setup.mjs +1 -1
  41. package/dist/astro/middleware.d.mts +13 -10
  42. package/dist/astro/middleware.d.mts.map +1 -1
  43. package/dist/astro/middleware.mjs +122 -3041
  44. package/dist/astro/middleware.mjs.map +1 -1
  45. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +10 -9
  46. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +10 -9
  48. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  49. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  50. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  51. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  52. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +13 -12
  53. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  54. package/dist/astro/routes/api/admin/byline-fields/index.mjs +13 -12
  55. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  56. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +13 -12
  57. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +22 -21
  59. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  60. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +22 -21
  61. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  62. package/dist/astro/routes/api/admin/bylines/index.mjs +22 -21
  63. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/comments/_id_/status.d.mts.map +1 -1
  65. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +21 -54
  66. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -8
  68. package/dist/astro/routes/api/admin/comments/bulk.mjs +15 -14
  69. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/comments/counts.mjs +8 -8
  71. package/dist/astro/routes/api/admin/comments/index.mjs +15 -14
  72. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
  74. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
  75. package/dist/astro/routes/api/admin/media-usage/activation.mjs +17 -16
  76. package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +19 -18
  78. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +19 -18
  80. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/media-usage/progress.mjs +17 -17
  82. package/dist/astro/routes/api/admin/media-usage/repair.mjs +21 -20
  83. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +19 -18
  85. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +19 -18
  87. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  89. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  90. package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts.map +1 -1
  91. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +9 -66
  92. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts.map +1 -1
  94. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +9 -69
  95. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +66 -59
  97. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +6 -6
  99. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +65 -59
  100. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  101. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +70 -61
  102. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts.map +1 -1
  104. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +87 -63
  105. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  106. package/dist/astro/routes/api/admin/plugins/index.mjs +66 -59
  107. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  108. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  109. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +65 -59
  110. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  111. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts.map +1 -1
  112. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +85 -63
  113. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  114. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +65 -59
  115. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  116. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +70 -61
  117. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  118. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.d.mts.map +1 -1
  119. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +90 -66
  120. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  121. package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
  122. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +9 -7
  123. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  124. package/dist/astro/routes/api/admin/plugins/registry/install.d.mts.map +1 -1
  125. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +77 -63
  126. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  127. package/dist/astro/routes/api/admin/plugins/registry/verify.d.mts.map +1 -1
  128. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs +67 -61
  129. package/dist/astro/routes/api/admin/plugins/registry/verify.mjs.map +1 -1
  130. package/dist/astro/routes/api/admin/plugins/updates.d.mts.map +1 -1
  131. package/dist/astro/routes/api/admin/plugins/updates.mjs +66 -60
  132. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  133. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.d.mts +8 -0
  134. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.d.mts.map +1 -0
  135. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs +27 -0
  136. package/dist/astro/routes/api/admin/scheduled-policy-rejections/_collection_/_id_.mjs.map +1 -0
  137. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +65 -59
  138. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  139. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  140. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +65 -59
  141. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  142. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  143. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  144. package/dist/astro/routes/api/admin/users/_id_/index.mjs +11 -10
  145. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  146. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
  147. package/dist/astro/routes/api/admin/users/index.mjs +10 -9
  148. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  149. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  150. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  151. package/dist/astro/routes/api/auth/invite/complete.mjs +15 -14
  152. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  153. package/dist/astro/routes/api/auth/invite/index.mjs +12 -11
  154. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  155. package/dist/astro/routes/api/auth/invite/register-options.mjs +14 -13
  156. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  157. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  158. package/dist/astro/routes/api/auth/magic-link/send.mjs +14 -13
  159. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  160. package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
  161. package/dist/astro/routes/api/auth/me.mjs +11 -10
  162. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  163. package/dist/astro/routes/api/auth/mode.mjs +4 -4
  164. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  165. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  166. package/dist/astro/routes/api/auth/passkey/_id_.mjs +10 -9
  167. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  168. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  169. package/dist/astro/routes/api/auth/passkey/options.mjs +16 -15
  170. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  171. package/dist/astro/routes/api/auth/passkey/register/options.mjs +14 -13
  172. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  173. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +15 -14
  174. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  175. package/dist/astro/routes/api/auth/passkey/verify.mjs +15 -14
  176. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  177. package/dist/astro/routes/api/auth/signup/complete.mjs +15 -14
  178. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  179. package/dist/astro/routes/api/auth/signup/request.mjs +14 -13
  180. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  181. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  182. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts.map +1 -1
  183. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +15 -211
  184. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  185. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +18 -17
  186. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -1
  187. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  188. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts.map +1 -1
  189. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +17 -11
  190. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  191. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +5 -5
  192. package/dist/astro/routes/api/content/_collection_/_id_/lock.d.mts +10 -0
  193. package/dist/astro/routes/api/content/_collection_/_id_/lock.d.mts.map +1 -0
  194. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs +67 -0
  195. package/dist/astro/routes/api/content/_collection_/_id_/lock.mjs.map +1 -0
  196. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  197. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.d.mts +8 -0
  198. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.d.mts.map +1 -0
  199. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs +35 -0
  200. package/dist/astro/routes/api/content/_collection_/_id_/plugin-extensions/_pluginId_/_kind_/_extensionId_.mjs.map +1 -0
  201. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +16 -15
  202. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  203. package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts +9 -2
  204. package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts.map +1 -1
  205. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +28 -16
  206. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  207. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +27 -23
  208. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  209. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +27 -23
  210. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  211. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +5 -5
  212. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  213. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  214. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +27 -12
  215. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  216. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +23 -20
  217. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  218. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
  219. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts.map +1 -1
  220. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +25 -12
  221. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  222. package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
  223. package/dist/astro/routes/api/content/_collection_/_id_.mjs +27 -14
  224. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  225. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  226. package/dist/astro/routes/api/content/_collection_/index.mjs +18 -12
  227. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  228. package/dist/astro/routes/api/content/_collection_/trash.mjs +11 -10
  229. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  230. package/dist/astro/routes/api/dashboard.mjs +14 -12
  231. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  232. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  233. package/dist/astro/routes/api/health.d.mts +8 -0
  234. package/dist/astro/routes/api/health.d.mts.map +1 -0
  235. package/dist/astro/routes/api/health.mjs +21 -0
  236. package/dist/astro/routes/api/health.mjs.map +1 -0
  237. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  238. package/dist/astro/routes/api/import/probe.mjs +25 -23
  239. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  240. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
  241. package/dist/astro/routes/api/import/wordpress/execute.d.mts +13 -11
  242. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  243. package/dist/astro/routes/api/import/wordpress/execute.mjs +20 -19
  244. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  245. package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -10
  246. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  247. package/dist/astro/routes/api/import/wordpress/media.mjs +15 -14
  248. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  249. package/dist/astro/routes/api/import/wordpress/prepare.mjs +14 -13
  250. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  251. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +13 -11
  252. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  253. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +18 -17
  254. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  255. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  256. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +25 -23
  257. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  258. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  259. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +13 -11
  260. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  261. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +36 -33
  262. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  263. package/dist/astro/routes/api/manifest.mjs +17 -9
  264. package/dist/astro/routes/api/manifest.mjs.map +1 -1
  265. package/dist/astro/routes/api/mcp.d.mts.map +1 -1
  266. package/dist/astro/routes/api/mcp.mjs +192 -110
  267. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  268. package/dist/astro/routes/api/media/_id_/confirm.mjs +15 -14
  269. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  270. package/dist/astro/routes/api/media/_id_/replace.mjs +12 -11
  271. package/dist/astro/routes/api/media/_id_/replace.mjs.map +1 -1
  272. package/dist/astro/routes/api/media/_id_/upload.mjs +8 -8
  273. package/dist/astro/routes/api/media/_id_/usage.mjs +23 -22
  274. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  275. package/dist/astro/routes/api/media/_id_.mjs +22 -21
  276. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  277. package/dist/astro/routes/api/media/asset/_id_/_filename_.d.mts +8 -0
  278. package/dist/astro/routes/api/media/asset/_id_/_filename_.d.mts.map +1 -0
  279. package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs +52 -0
  280. package/dist/astro/routes/api/media/asset/_id_/_filename_.mjs.map +1 -0
  281. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
  282. package/dist/astro/routes/api/media/folders/_id_.mjs +12 -11
  283. package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -1
  284. package/dist/astro/routes/api/media/folders/index.mjs +12 -11
  285. package/dist/astro/routes/api/media/folders/index.mjs.map +1 -1
  286. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  287. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  288. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  289. package/dist/astro/routes/api/media/upload-url.mjs +14 -13
  290. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  291. package/dist/astro/routes/api/media.mjs +26 -25
  292. package/dist/astro/routes/api/media.mjs.map +1 -1
  293. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +15 -14
  294. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  295. package/dist/astro/routes/api/menus/_name_/items.mjs +15 -14
  296. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  297. package/dist/astro/routes/api/menus/_name_/reorder.mjs +15 -14
  298. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  299. package/dist/astro/routes/api/menus/_name_/translations.mjs +15 -14
  300. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  301. package/dist/astro/routes/api/menus/_name_.mjs +15 -14
  302. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  303. package/dist/astro/routes/api/menus/index.mjs +15 -14
  304. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  305. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  306. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  307. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  308. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  309. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  310. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  311. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  312. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  313. package/dist/astro/routes/api/openapi.json.mjs +95 -24
  314. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  315. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts +2 -1
  316. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts.map +1 -1
  317. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +22 -40
  318. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs.map +1 -1
  319. package/dist/astro/routes/api/redirects/404s/index.mjs +18 -14
  320. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  321. package/dist/astro/routes/api/redirects/404s/summary.mjs +18 -14
  322. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  323. package/dist/astro/routes/api/redirects/_id_.d.mts.map +1 -1
  324. package/dist/astro/routes/api/redirects/_id_.mjs +20 -22
  325. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  326. package/dist/astro/routes/api/redirects/index.d.mts.map +1 -1
  327. package/dist/astro/routes/api/redirects/index.mjs +19 -19
  328. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  329. package/dist/astro/routes/api/relations/_id_/index.mjs +27 -23
  330. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  331. package/dist/astro/routes/api/relations/_id_/translations.mjs +21 -18
  332. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  333. package/dist/astro/routes/api/relations/index.mjs +27 -23
  334. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  335. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  336. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +5 -5
  337. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +65 -59
  338. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  339. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +65 -59
  340. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  341. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +65 -59
  342. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  343. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +65 -59
  344. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  345. package/dist/astro/routes/api/schema/collections/index.mjs +65 -59
  346. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  347. package/dist/astro/routes/api/schema/collections/reorder.mjs +65 -59
  348. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  349. package/dist/astro/routes/api/schema/index.mjs +16 -14
  350. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  351. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +65 -59
  352. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  353. package/dist/astro/routes/api/schema/orphans/index.mjs +65 -59
  354. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  355. package/dist/astro/routes/api/search/enable.mjs +16 -14
  356. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  357. package/dist/astro/routes/api/search/index.mjs +17 -14
  358. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  359. package/dist/astro/routes/api/search/rebuild.mjs +16 -14
  360. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  361. package/dist/astro/routes/api/search/stats.mjs +10 -9
  362. package/dist/astro/routes/api/search/stats.mjs.map +1 -1
  363. package/dist/astro/routes/api/search/suggest.mjs +15 -13
  364. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  365. package/dist/astro/routes/api/sections/_slug_.mjs +15 -13
  366. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  367. package/dist/astro/routes/api/sections/index.mjs +15 -13
  368. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  369. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +9 -8
  370. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs.map +1 -1
  371. package/dist/astro/routes/api/settings/backups/archives/index.mjs +8 -7
  372. package/dist/astro/routes/api/settings/backups/archives/index.mjs.map +1 -1
  373. package/dist/astro/routes/api/settings/backups/export.mjs +9 -8
  374. package/dist/astro/routes/api/settings/backups/export.mjs.map +1 -1
  375. package/dist/astro/routes/api/settings/backups/index.mjs +10 -9
  376. package/dist/astro/routes/api/settings/backups/index.mjs.map +1 -1
  377. package/dist/astro/routes/api/settings/email.mjs +6 -6
  378. package/dist/astro/routes/api/settings.mjs +22 -20
  379. package/dist/astro/routes/api/settings.mjs.map +1 -1
  380. package/dist/astro/routes/api/setup/admin-verify.mjs +16 -15
  381. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  382. package/dist/astro/routes/api/setup/admin.mjs +15 -14
  383. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  384. package/dist/astro/routes/api/setup/dev-bypass.mjs +42 -38
  385. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  386. package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
  387. package/dist/astro/routes/api/setup/index.mjs +43 -38
  388. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  389. package/dist/astro/routes/api/setup/status.mjs +5 -5
  390. package/dist/astro/routes/api/snapshot.mjs +11 -10
  391. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  392. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +23 -21
  393. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs.map +1 -1
  394. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +23 -21
  395. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  396. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +23 -21
  397. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  398. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +23 -21
  399. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  400. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +22 -20
  401. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs.map +1 -1
  402. package/dist/astro/routes/api/taxonomies/_name_.mjs +23 -21
  403. package/dist/astro/routes/api/taxonomies/_name_.mjs.map +1 -1
  404. package/dist/astro/routes/api/taxonomies/index.mjs +23 -21
  405. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  406. package/dist/astro/routes/api/themes/preview.mjs +8 -8
  407. package/dist/astro/routes/api/typegen.mjs +6 -6
  408. package/dist/astro/routes/api/visual-editing/action-token.d.mts +8 -0
  409. package/dist/astro/routes/api/visual-editing/action-token.d.mts.map +1 -0
  410. package/dist/astro/routes/api/visual-editing/action-token.mjs +25 -0
  411. package/dist/astro/routes/api/visual-editing/action-token.mjs.map +1 -0
  412. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.d.mts +8 -0
  413. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.d.mts.map +1 -0
  414. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs +37 -0
  415. package/dist/astro/routes/api/visual-editing/content/_collection_/_id_/publish.mjs.map +1 -0
  416. package/dist/astro/routes/api/visual-editing/toolbar-labels.d.mts +8 -0
  417. package/dist/astro/routes/api/visual-editing/toolbar-labels.d.mts.map +1 -0
  418. package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs +20 -0
  419. package/dist/astro/routes/api/visual-editing/toolbar-labels.mjs.map +1 -0
  420. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  421. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  422. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  423. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +12 -11
  424. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  425. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +16 -15
  426. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  427. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +16 -15
  428. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  429. package/dist/astro/routes/api/widget-areas/_name_.mjs +10 -10
  430. package/dist/astro/routes/api/widget-areas/index.mjs +16 -15
  431. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  432. package/dist/astro/routes/api/widget-components.mjs +4 -4
  433. package/dist/astro/routes/robots.txt.mjs +13 -12
  434. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  435. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  436. package/dist/astro/routes/sitemap-_collection_.xml.mjs +24 -19
  437. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  438. package/dist/astro/routes/sitemap.xml.mjs +14 -13
  439. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  440. package/dist/astro/types.d.mts +79 -26
  441. package/dist/astro/types.d.mts.map +1 -1
  442. package/dist/auth/providers/github.d.mts +1 -1
  443. package/dist/auth/providers/google.d.mts +1 -1
  444. package/dist/authoritative-records-DbqshfKD.mjs +5172 -0
  445. package/dist/authoritative-records-DbqshfKD.mjs.map +1 -0
  446. package/dist/authorize-BXpfMpp0.mjs +40 -0
  447. package/dist/authorize-BXpfMpp0.mjs.map +1 -0
  448. package/dist/backup-BsPQdwnS.mjs +246 -0
  449. package/dist/backup-BsPQdwnS.mjs.map +1 -0
  450. package/dist/base64-DvnNgP7e.mjs +63 -0
  451. package/dist/base64-DvnNgP7e.mjs.map +1 -0
  452. package/dist/byline-Doh-wt99.mjs +906 -0
  453. package/dist/byline-Doh-wt99.mjs.map +1 -0
  454. package/dist/byline-fields-CsrdvaLV.mjs +123 -0
  455. package/dist/byline-fields-CsrdvaLV.mjs.map +1 -0
  456. package/dist/byline-registry-D6lWzXk6.mjs +406 -0
  457. package/dist/byline-registry-D6lWzXk6.mjs.map +1 -0
  458. package/dist/bylines-Cop0hkoy.mjs +214 -0
  459. package/dist/bylines-Cop0hkoy.mjs.map +1 -0
  460. package/dist/bylines-kpBzHPr8.mjs +232 -0
  461. package/dist/bylines-kpBzHPr8.mjs.map +1 -0
  462. package/dist/cache-B0pOlXdM.mjs +108 -0
  463. package/dist/cache-B0pOlXdM.mjs.map +1 -0
  464. package/dist/capture-triggers-gxQxnxrJ.mjs +362 -0
  465. package/dist/capture-triggers-gxQxnxrJ.mjs.map +1 -0
  466. package/dist/challenge-store-CkRdSdd8.mjs +51 -0
  467. package/dist/challenge-store-CkRdSdd8.mjs.map +1 -0
  468. package/dist/checksum-Bsi2D9M1.mjs +162 -0
  469. package/dist/checksum-Bsi2D9M1.mjs.map +1 -0
  470. package/dist/chrome-tags-D7LGB91U.mjs +24 -0
  471. package/dist/chrome-tags-D7LGB91U.mjs.map +1 -0
  472. package/dist/chunks-BmWyou79.mjs +25 -0
  473. package/dist/chunks-BmWyou79.mjs.map +1 -0
  474. package/dist/cli/index.mjs +921 -106
  475. package/dist/cli/index.mjs.map +1 -1
  476. package/dist/client/cf-access.d.mts +1 -1
  477. package/dist/client/index.d.mts +17 -7
  478. package/dist/client/index.d.mts.map +1 -1
  479. package/dist/client/index.mjs +17 -12
  480. package/dist/client/index.mjs.map +1 -1
  481. package/dist/collection-deletion-C3Xixmkx.mjs +436 -0
  482. package/dist/collection-deletion-C3Xixmkx.mjs.map +1 -0
  483. package/dist/collection-slugs-cache-D9auiKmI.mjs +85 -0
  484. package/dist/collection-slugs-cache-D9auiKmI.mjs.map +1 -0
  485. package/dist/comment-DcT8qdPK.mjs +313 -0
  486. package/dist/comment-DcT8qdPK.mjs.map +1 -0
  487. package/dist/comment-reaction-DY5-y8Y5.mjs +85 -0
  488. package/dist/comment-reaction-DY5-y8Y5.mjs.map +1 -0
  489. package/dist/comments-B67VRNGw.mjs +204 -0
  490. package/dist/comments-B67VRNGw.mjs.map +1 -0
  491. package/dist/comments-BVzzt6x5.mjs +99 -0
  492. package/dist/comments-BVzzt6x5.mjs.map +1 -0
  493. package/dist/components-DIl9Qzh6.mjs +108 -0
  494. package/dist/components-DIl9Qzh6.mjs.map +1 -0
  495. package/dist/config-B2acLeC5.mjs +242 -0
  496. package/dist/config-B2acLeC5.mjs.map +1 -0
  497. package/dist/config-CGVeT8Hc.mjs +73 -0
  498. package/dist/config-CGVeT8Hc.mjs.map +1 -0
  499. package/dist/config-D3HhLON3.d.mts +39 -0
  500. package/dist/config-D3HhLON3.d.mts.map +1 -0
  501. package/dist/config-DS7e4EZ9.d.mts +6387 -0
  502. package/dist/config-DS7e4EZ9.d.mts.map +1 -0
  503. package/dist/content-DZ3B5qtA.mjs +2216 -0
  504. package/dist/content-DZ3B5qtA.mjs.map +1 -0
  505. package/dist/content-list-query-CbPdzsna.d.mts +26 -0
  506. package/dist/content-list-query-CbPdzsna.d.mts.map +1 -0
  507. package/dist/content-policy-CM4skrNi.mjs +46 -0
  508. package/dist/content-policy-CM4skrNi.mjs.map +1 -0
  509. package/dist/content-refresh-BHLHJgyt.mjs +3599 -0
  510. package/dist/content-refresh-BHLHJgyt.mjs.map +1 -0
  511. package/dist/content-repair-BVll5WNl.mjs +440 -0
  512. package/dist/content-repair-BVll5WNl.mjs.map +1 -0
  513. package/dist/context-B195kjqZ.mjs +1835 -0
  514. package/dist/context-B195kjqZ.mjs.map +1 -0
  515. package/dist/cron-BcR3RN4K.mjs +282 -0
  516. package/dist/cron-BcR3RN4K.mjs.map +1 -0
  517. package/dist/dashboard-CZD4G7k_.mjs +151 -0
  518. package/dist/dashboard-CZD4G7k_.mjs.map +1 -0
  519. package/dist/database/migration-lock.d.mts +37 -0
  520. package/dist/database/migration-lock.d.mts.map +1 -0
  521. package/dist/database/migration-lock.mjs +76 -0
  522. package/dist/database/migration-lock.mjs.map +1 -0
  523. package/dist/database/pg-migration-lock.d.mts +1 -1
  524. package/dist/database/pg-migration-lock.mjs +1 -1
  525. package/dist/database/pg-migration-lock.mjs.map +1 -1
  526. package/dist/datetime-normalization-PeiEdcRY.mjs +187 -0
  527. package/dist/datetime-normalization-PeiEdcRY.mjs.map +1 -0
  528. package/dist/db/index.d.mts +3 -3
  529. package/dist/db/index.mjs +4 -3
  530. package/dist/db/index.mjs.map +1 -1
  531. package/dist/db/libsql-migrations.d.mts +1 -1
  532. package/dist/db/libsql-migrations.mjs +7 -6
  533. package/dist/db/libsql-migrations.mjs.map +1 -1
  534. package/dist/db/libsql.d.mts +1 -1
  535. package/dist/db/postgres-migrations.d.mts +1 -1
  536. package/dist/db/postgres-migrations.mjs +7 -6
  537. package/dist/db/postgres-migrations.mjs.map +1 -1
  538. package/dist/db/postgres.d.mts +1 -1
  539. package/dist/db/sqlite-migrations.d.mts +1 -1
  540. package/dist/db/sqlite-migrations.mjs +7 -6
  541. package/dist/db/sqlite-migrations.mjs.map +1 -1
  542. package/dist/db/sqlite.d.mts +1 -1
  543. package/dist/db/sqlite.mjs +1 -1
  544. package/dist/db-errors-CBD5yMro.mjs +55 -0
  545. package/dist/db-errors-CBD5yMro.mjs.map +1 -0
  546. package/dist/default-BtlxWgh9.mjs +81 -0
  547. package/dist/default-BtlxWgh9.mjs.map +1 -0
  548. package/dist/deferred-tasks-Ba9fphlE.d.mts +9 -0
  549. package/dist/deferred-tasks-Ba9fphlE.d.mts.map +1 -0
  550. package/dist/dev-typegen-CZaPLl3V.mjs +29 -0
  551. package/dist/dev-typegen-CZaPLl3V.mjs.map +1 -0
  552. package/dist/device-flow-3E5l0fpH.mjs +466 -0
  553. package/dist/device-flow-3E5l0fpH.mjs.map +1 -0
  554. package/dist/dialect-helpers-CtYdsX4W.mjs +307 -0
  555. package/dist/dialect-helpers-CtYdsX4W.mjs.map +1 -0
  556. package/dist/direct-executor-ZI8E01Yt.mjs +104 -0
  557. package/dist/direct-executor-ZI8E01Yt.mjs.map +1 -0
  558. package/dist/dist-2hDLsLeq.mjs +810 -0
  559. package/dist/dist-2hDLsLeq.mjs.map +1 -0
  560. package/dist/email-console-gc-E3Eqj.mjs +50 -0
  561. package/dist/email-console-gc-E3Eqj.mjs.map +1 -0
  562. package/dist/emdash-runtime-DZGinqTL.mjs +4041 -0
  563. package/dist/emdash-runtime-DZGinqTL.mjs.map +1 -0
  564. package/dist/enrich-C2Mk0lwE.mjs +194 -0
  565. package/dist/enrich-C2Mk0lwE.mjs.map +1 -0
  566. package/dist/entry-lock-D3C99MXS.mjs +1520 -0
  567. package/dist/entry-lock-D3C99MXS.mjs.map +1 -0
  568. package/dist/entry-lock-D9rnj2Lu.mjs +163 -0
  569. package/dist/entry-lock-D9rnj2Lu.mjs.map +1 -0
  570. package/dist/entry-lock-DX7QkqEg.d.mts +3503 -0
  571. package/dist/entry-lock-DX7QkqEg.d.mts.map +1 -0
  572. package/dist/entry-locks-CO_KaoU5.mjs +167 -0
  573. package/dist/entry-locks-CO_KaoU5.mjs.map +1 -0
  574. package/dist/error-CCTCskie.mjs +90 -0
  575. package/dist/error-CCTCskie.mjs.map +1 -0
  576. package/dist/errors-W3PA8HqB.mjs +447 -0
  577. package/dist/errors-W3PA8HqB.mjs.map +1 -0
  578. package/dist/escape-DN4Qmjsl.mjs +9 -0
  579. package/dist/escape-DN4Qmjsl.mjs.map +1 -0
  580. package/dist/field-defs-cache-B337z8UJ.mjs +77 -0
  581. package/dist/field-defs-cache-B337z8UJ.mjs.map +1 -0
  582. package/dist/focal-point-Gr34o-4o.mjs +23 -0
  583. package/dist/focal-point-Gr34o-4o.mjs.map +1 -0
  584. package/dist/fts-manager-DMb0sbIQ.mjs +413 -0
  585. package/dist/fts-manager-DMb0sbIQ.mjs.map +1 -0
  586. package/dist/hash-CLVFzWvx.mjs +37 -0
  587. package/dist/hash-CLVFzWvx.mjs.map +1 -0
  588. package/dist/hook-result-KY1jq21o.d.mts +31 -0
  589. package/dist/hook-result-KY1jq21o.d.mts.map +1 -0
  590. package/dist/hreflang-BgS2FqhH.d.mts +50 -0
  591. package/dist/hreflang-BgS2FqhH.d.mts.map +1 -0
  592. package/dist/hreflang-CrJwqoeb.mjs +105 -0
  593. package/dist/hreflang-CrJwqoeb.mjs.map +1 -0
  594. package/dist/http-route-dispatch-DAKlanXb.mjs +339 -0
  595. package/dist/http-route-dispatch-DAKlanXb.mjs.map +1 -0
  596. package/dist/identity-DRGSHEEr.mjs +35 -0
  597. package/dist/identity-DRGSHEEr.mjs.map +1 -0
  598. package/dist/import-DFr5B_hC.mjs +2020 -0
  599. package/dist/import-DFr5B_hC.mjs.map +1 -0
  600. package/dist/index-CDwKDkTq.d.mts +623 -0
  601. package/dist/index-CDwKDkTq.d.mts.map +1 -0
  602. package/dist/index.d.mts +27 -22
  603. package/dist/index.mjs +101 -86
  604. package/dist/init-lock-4eRRB4Le.mjs +56 -0
  605. package/dist/init-lock-4eRRB4Le.mjs.map +1 -0
  606. package/dist/install-finalization-5Os-QZ-2.mjs +58 -0
  607. package/dist/install-finalization-5Os-QZ-2.mjs.map +1 -0
  608. package/dist/lifecycle-7e-Lt_kX.mjs +25 -0
  609. package/dist/lifecycle-7e-Lt_kX.mjs.map +1 -0
  610. package/dist/load-Cu5bvCZh.mjs +28 -0
  611. package/dist/load-Cu5bvCZh.mjs.map +1 -0
  612. package/dist/loader-t4TxaFiQ.mjs +1004 -0
  613. package/dist/loader-t4TxaFiQ.mjs.map +1 -0
  614. package/dist/manifest-builder-D31UoJCu.mjs +189 -0
  615. package/dist/manifest-builder-D31UoJCu.mjs.map +1 -0
  616. package/dist/manifest-schema-Bkh8SeHd.mjs +491 -0
  617. package/dist/manifest-schema-Bkh8SeHd.mjs.map +1 -0
  618. package/dist/media/image-endpoint.mjs +1 -1
  619. package/dist/media/index.d.mts +1 -1
  620. package/dist/media/index.mjs +4 -4
  621. package/dist/media/local-runtime.d.mts +14 -12
  622. package/dist/media/local-runtime.d.mts.map +1 -1
  623. package/dist/media/local-runtime.mjs +12 -11
  624. package/dist/media/local-runtime.mjs.map +1 -1
  625. package/dist/media-TBlKCwo7.mjs +334 -0
  626. package/dist/media-TBlKCwo7.mjs.map +1 -0
  627. package/dist/media-allowlist-s60eAIf6.mjs +44 -0
  628. package/dist/media-allowlist-s60eAIf6.mjs.map +1 -0
  629. package/dist/media-folders-ClIRLUQv.mjs +210 -0
  630. package/dist/media-folders-ClIRLUQv.mjs.map +1 -0
  631. package/dist/media-url-DY_ijO3B.mjs +43 -0
  632. package/dist/media-url-DY_ijO3B.mjs.map +1 -0
  633. package/dist/media-usage-C4NolTpQ.mjs +1833 -0
  634. package/dist/media-usage-C4NolTpQ.mjs.map +1 -0
  635. package/dist/media-usage-activation-CAhmb14O.mjs +78 -0
  636. package/dist/media-usage-activation-CAhmb14O.mjs.map +1 -0
  637. package/dist/media-usage-work-Cu5DqewW.mjs +586 -0
  638. package/dist/media-usage-work-Cu5DqewW.mjs.map +1 -0
  639. package/dist/media-usage-work-DJr3rOEN.mjs +152 -0
  640. package/dist/media-usage-work-DJr3rOEN.mjs.map +1 -0
  641. package/dist/media-usage-write-fence-DW5oHT7q.mjs +42 -0
  642. package/dist/media-usage-write-fence-DW5oHT7q.mjs.map +1 -0
  643. package/dist/menus-DbgU9z5Q.mjs +3732 -0
  644. package/dist/menus-DbgU9z5Q.mjs.map +1 -0
  645. package/dist/menus-Wy8m5NcH.mjs +753 -0
  646. package/dist/menus-Wy8m5NcH.mjs.map +1 -0
  647. package/dist/migrations/index.d.mts +2 -2
  648. package/dist/migrations/index.d.mts.map +1 -1
  649. package/dist/migrations/index.mjs +6 -5
  650. package/dist/mime-CCYRC4r1.mjs +36 -0
  651. package/dist/mime-CCYRC4r1.mjs.map +1 -0
  652. package/dist/mode-CkmOhFqj.mjs +23 -0
  653. package/dist/mode-CkmOhFqj.mjs.map +1 -0
  654. package/dist/node-sqlite-compat-vpVaILnb.mjs +46 -0
  655. package/dist/node-sqlite-compat-vpVaILnb.mjs.map +1 -0
  656. package/dist/normalize-dChXQjwS.mjs +191 -0
  657. package/dist/normalize-dChXQjwS.mjs.map +1 -0
  658. package/dist/oauth-authorization-DZ-R-hmx.mjs +274 -0
  659. package/dist/oauth-authorization-DZ-R-hmx.mjs.map +1 -0
  660. package/dist/oauth-clients-BMPA8vjl.mjs +266 -0
  661. package/dist/oauth-clients-BMPA8vjl.mjs.map +1 -0
  662. package/dist/oauth-state-store-0gIdb48-.mjs +50 -0
  663. package/dist/oauth-state-store-0gIdb48-.mjs.map +1 -0
  664. package/dist/oauth-user-lookup-ChVW4UWu.mjs +26 -0
  665. package/dist/oauth-user-lookup-ChVW4UWu.mjs.map +1 -0
  666. package/dist/object-cache/memory.d.mts +1 -1
  667. package/dist/object-cache-DlSVbn5Y.mjs +534 -0
  668. package/dist/object-cache-DlSVbn5Y.mjs.map +1 -0
  669. package/dist/options-D5WcorD5.mjs +200 -0
  670. package/dist/options-D5WcorD5.mjs.map +1 -0
  671. package/dist/options-DAknChnw.d.mts +77 -0
  672. package/dist/options-DAknChnw.d.mts.map +1 -0
  673. package/dist/page/index.d.mts +2 -2
  674. package/dist/page/index.d.mts.map +1 -1
  675. package/dist/page/index.mjs +13 -0
  676. package/dist/page/index.mjs.map +1 -1
  677. package/dist/parse-Cr379Av9.mjs +81 -0
  678. package/dist/parse-Cr379Av9.mjs.map +1 -0
  679. package/dist/parse-tj9XLNO_.d.mts +165 -0
  680. package/dist/parse-tj9XLNO_.d.mts.map +1 -0
  681. package/dist/passkey-config-BD-wMaCf.mjs +46 -0
  682. package/dist/passkey-config-BD-wMaCf.mjs.map +1 -0
  683. package/dist/patterns-Do3iqi7N.mjs +145 -0
  684. package/dist/patterns-Do3iqi7N.mjs.map +1 -0
  685. package/dist/placeholder-CubPFn_L.d.mts +306 -0
  686. package/dist/placeholder-CubPFn_L.d.mts.map +1 -0
  687. package/dist/plugin-test-runtime.d.mts +97 -0
  688. package/dist/plugin-test-runtime.d.mts.map +1 -0
  689. package/dist/plugin-test-runtime.mjs +105 -0
  690. package/dist/plugin-types.d.mts +53 -15
  691. package/dist/plugin-types.d.mts.map +1 -1
  692. package/dist/plugin-types.mjs +18 -1
  693. package/dist/plugin-types.mjs.map +1 -0
  694. package/dist/plugin-utils.d.mts +12 -10
  695. package/dist/plugin-utils.d.mts.map +1 -1
  696. package/dist/plugins/adapt-sandbox-entry.d.mts +12 -10
  697. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  698. package/dist/plugins/adapt-sandbox-entry.mjs +18 -13
  699. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  700. package/dist/plugins/host.d.mts +7 -0
  701. package/dist/plugins/host.mjs +45 -0
  702. package/dist/plugins/http-wire.d.mts +25 -0
  703. package/dist/plugins/http-wire.d.mts.map +1 -0
  704. package/dist/plugins/http-wire.mjs +148 -0
  705. package/dist/plugins/http-wire.mjs.map +1 -0
  706. package/dist/plugins/secret-redactor.d.mts +9 -0
  707. package/dist/plugins/secret-redactor.d.mts.map +1 -0
  708. package/dist/plugins/secret-redactor.mjs +51 -0
  709. package/dist/plugins/secret-redactor.mjs.map +1 -0
  710. package/dist/plugins-DHurtdXQ.mjs +277 -0
  711. package/dist/plugins-DHurtdXQ.mjs.map +1 -0
  712. package/dist/policy-lyZnjpwz.mjs +45 -0
  713. package/dist/policy-lyZnjpwz.mjs.map +1 -0
  714. package/dist/portable-text-oAwodbPS.mjs +286 -0
  715. package/dist/portable-text-oAwodbPS.mjs.map +1 -0
  716. package/dist/preview-Cup2bj8c.mjs +107 -0
  717. package/dist/preview-Cup2bj8c.mjs.map +1 -0
  718. package/dist/protocol-C1CNiKX4.d.mts +61 -0
  719. package/dist/protocol-C1CNiKX4.d.mts.map +1 -0
  720. package/dist/public-submission-Cs-pFMQK.mjs +245 -0
  721. package/dist/public-submission-Cs-pFMQK.mjs.map +1 -0
  722. package/dist/public-url-BWVHsfQv.mjs +101 -0
  723. package/dist/public-url-BWVHsfQv.mjs.map +1 -0
  724. package/dist/query-D-lxiOgn.mjs +890 -0
  725. package/dist/query-D-lxiOgn.mjs.map +1 -0
  726. package/dist/query-D7kHKOws.mjs +41 -0
  727. package/dist/query-D7kHKOws.mjs.map +1 -0
  728. package/dist/rate-limit-CqJn-w4h.mjs +120 -0
  729. package/dist/rate-limit-CqJn-w4h.mjs.map +1 -0
  730. package/dist/redirect-BrChFPIu.mjs +17 -0
  731. package/dist/redirect-BrChFPIu.mjs.map +1 -0
  732. package/dist/redirect-D2Yo-b6S.mjs +415 -0
  733. package/dist/redirect-D2Yo-b6S.mjs.map +1 -0
  734. package/dist/redirects-BCiTaIQ-.mjs +146 -0
  735. package/dist/redirects-BCiTaIQ-.mjs.map +1 -0
  736. package/dist/redirects-ByItliuu.mjs +644 -0
  737. package/dist/redirects-ByItliuu.mjs.map +1 -0
  738. package/dist/registry/testing.d.mts +14 -0
  739. package/dist/registry/testing.d.mts.map +1 -0
  740. package/dist/registry/testing.mjs +255 -0
  741. package/dist/registry/testing.mjs.map +1 -0
  742. package/dist/registry-CkuPoZs7.mjs +1222 -0
  743. package/dist/registry-CkuPoZs7.mjs.map +1 -0
  744. package/dist/relation-XTAtOpJO.mjs +334 -0
  745. package/dist/relation-XTAtOpJO.mjs.map +1 -0
  746. package/dist/relations-B4w-G5U9.mjs +309 -0
  747. package/dist/relations-B4w-G5U9.mjs.map +1 -0
  748. package/dist/relations-O63JWBRj.mjs +403 -0
  749. package/dist/relations-O63JWBRj.mjs.map +1 -0
  750. package/dist/request-cache-DaMhSQdx.mjs +104 -0
  751. package/dist/request-cache-DaMhSQdx.mjs.map +1 -0
  752. package/dist/request-context.d.mts +1 -1
  753. package/dist/request-meta-C8yl_sIs.mjs +141 -0
  754. package/dist/request-meta-C8yl_sIs.mjs.map +1 -0
  755. package/dist/resolve-BJIS8wKr.mjs +199 -0
  756. package/dist/resolve-BJIS8wKr.mjs.map +1 -0
  757. package/dist/route-wire-D66L2oaZ.mjs +197 -0
  758. package/dist/route-wire-D66L2oaZ.mjs.map +1 -0
  759. package/dist/routes-DYsTe-oR.mjs +321 -0
  760. package/dist/routes-DYsTe-oR.mjs.map +1 -0
  761. package/dist/runner-3gfxRsJG.d.mts +68 -0
  762. package/dist/runner-3gfxRsJG.d.mts.map +1 -0
  763. package/dist/runner-ClrleR8A.mjs +5502 -0
  764. package/dist/runner-ClrleR8A.mjs.map +1 -0
  765. package/dist/runtime.d.mts +13 -11
  766. package/dist/runtime.d.mts.map +1 -1
  767. package/dist/runtime.mjs +6 -5
  768. package/dist/runtime.mjs.map +1 -1
  769. package/dist/schema-CPhPM9Do.mjs +420 -0
  770. package/dist/schema-CPhPM9Do.mjs.map +1 -0
  771. package/dist/schema-DEERRrKS.mjs +9 -0
  772. package/dist/schema-DEERRrKS.mjs.map +1 -0
  773. package/dist/scopes-Bn8_rCSt.mjs +27 -0
  774. package/dist/scopes-Bn8_rCSt.mjs.map +1 -0
  775. package/dist/search-lami_Ota.mjs +448 -0
  776. package/dist/search-lami_Ota.mjs.map +1 -0
  777. package/dist/secrets-DNgVgz_e.mjs +341 -0
  778. package/dist/secrets-DNgVgz_e.mjs.map +1 -0
  779. package/dist/sections-BIpd4Cxf.mjs +346 -0
  780. package/dist/sections-BIpd4Cxf.mjs.map +1 -0
  781. package/dist/seed/index.d.mts +2 -2
  782. package/dist/seed/index.mjs +35 -31
  783. package/dist/seo/index.d.mts +3 -3
  784. package/dist/seo/index.d.mts.map +1 -1
  785. package/dist/seo/index.mjs +27 -26
  786. package/dist/seo/index.mjs.map +1 -1
  787. package/dist/seo-BtU70b7l.mjs +114 -0
  788. package/dist/seo-BtU70b7l.mjs.map +1 -0
  789. package/dist/seo-_4qmazxz.mjs +133 -0
  790. package/dist/seo-_4qmazxz.mjs.map +1 -0
  791. package/dist/service-Bpy5iTX0.mjs +150 -0
  792. package/dist/service-Bpy5iTX0.mjs.map +1 -0
  793. package/dist/session-user-BQeS2Mrb.mjs +51 -0
  794. package/dist/session-user-BQeS2Mrb.mjs.map +1 -0
  795. package/dist/settings-DmroR53o.mjs +420 -0
  796. package/dist/settings-DmroR53o.mjs.map +1 -0
  797. package/dist/settings-g2F2a3Ub.mjs +51 -0
  798. package/dist/settings-g2F2a3Ub.mjs.map +1 -0
  799. package/dist/setup-complete-BrZaXpVV.mjs +26 -0
  800. package/dist/setup-complete-BrZaXpVV.mjs.map +1 -0
  801. package/dist/setup-nonce-Cb9-884O.mjs +25 -0
  802. package/dist/setup-nonce-Cb9-884O.mjs.map +1 -0
  803. package/dist/single-flight-cache-B5O2qhO1.mjs +104 -0
  804. package/dist/single-flight-cache-B5O2qhO1.mjs.map +1 -0
  805. package/dist/site-url-DjhYCaep.mjs +16 -0
  806. package/dist/site-url-DjhYCaep.mjs.map +1 -0
  807. package/dist/slugify-T5GHH42D.mjs +17 -0
  808. package/dist/slugify-T5GHH42D.mjs.map +1 -0
  809. package/dist/snapshot-C32Xuv3o.mjs +233 -0
  810. package/dist/snapshot-C32Xuv3o.mjs.map +1 -0
  811. package/dist/ssrf-Co1GGFZz.mjs +406 -0
  812. package/dist/ssrf-Co1GGFZz.mjs.map +1 -0
  813. package/dist/state-C7gCoiHr.mjs +153 -0
  814. package/dist/state-C7gCoiHr.mjs.map +1 -0
  815. package/dist/status-DLoBmcDz.mjs +30 -0
  816. package/dist/status-DLoBmcDz.mjs.map +1 -0
  817. package/dist/storage/local.d.mts +1 -1
  818. package/dist/storage/local.mjs +1 -1
  819. package/dist/storage/s3.d.mts +1 -1
  820. package/dist/storage/s3.mjs +1 -1
  821. package/dist/storage-query-CPctdMKX.d.mts +1519 -0
  822. package/dist/storage-query-CPctdMKX.d.mts.map +1 -0
  823. package/dist/target-CUKlGrXb.mjs +27 -0
  824. package/dist/target-CUKlGrXb.mjs.map +1 -0
  825. package/dist/taxonomies-BpqpXJUE.mjs +707 -0
  826. package/dist/taxonomies-BpqpXJUE.mjs.map +1 -0
  827. package/dist/taxonomies-ClCo4IGO.mjs +899 -0
  828. package/dist/taxonomies-ClCo4IGO.mjs.map +1 -0
  829. package/dist/taxonomy-Dj1vpw5E.mjs +508 -0
  830. package/dist/taxonomy-Dj1vpw5E.mjs.map +1 -0
  831. package/dist/tokens-Ns02GAma.mjs +171 -0
  832. package/dist/tokens-Ns02GAma.mjs.map +1 -0
  833. package/dist/transaction-BcJO4Tmc.mjs +29 -0
  834. package/dist/transaction-BcJO4Tmc.mjs.map +1 -0
  835. package/dist/transport-B8P1xsqk.mjs +136 -0
  836. package/dist/transport-B8P1xsqk.mjs.map +1 -0
  837. package/dist/transport-BLHZZcD_.d.mts +42 -0
  838. package/dist/transport-BLHZZcD_.d.mts.map +1 -0
  839. package/dist/trusted-proxy-Cg7SuD15.mjs +51 -0
  840. package/dist/trusted-proxy-Cg7SuD15.mjs.map +1 -0
  841. package/dist/turnstile-DWCSh_Qc.mjs +53 -0
  842. package/dist/turnstile-DWCSh_Qc.mjs.map +1 -0
  843. package/dist/types-B3RkqvDW.mjs +156 -0
  844. package/dist/types-B3RkqvDW.mjs.map +1 -0
  845. package/dist/types-BkUg2yHx.mjs +92 -0
  846. package/dist/types-BkUg2yHx.mjs.map +1 -0
  847. package/dist/types-BtuppOTV.mjs +16 -0
  848. package/dist/types-BtuppOTV.mjs.map +1 -0
  849. package/dist/types-CCR3ivXE.d.mts +363 -0
  850. package/dist/types-CCR3ivXE.d.mts.map +1 -0
  851. package/dist/types-CSHgU_-_.mjs +18 -0
  852. package/dist/types-CSHgU_-_.mjs.map +1 -0
  853. package/dist/types-CbwqGOXm.d.mts +766 -0
  854. package/dist/types-CbwqGOXm.d.mts.map +1 -0
  855. package/dist/types-D4RGIc2I.d.mts +198 -0
  856. package/dist/types-D4RGIc2I.d.mts.map +1 -0
  857. package/dist/types-DEHidMEk.d.mts +1637 -0
  858. package/dist/types-DEHidMEk.d.mts.map +1 -0
  859. package/dist/types-DQeU2SR7.d.mts +279 -0
  860. package/dist/types-DQeU2SR7.d.mts.map +1 -0
  861. package/dist/types-DxQWoQLA.d.mts +264 -0
  862. package/dist/types-DxQWoQLA.d.mts.map +1 -0
  863. package/dist/types-FBBnGZ2Y.d.mts +198 -0
  864. package/dist/types-FBBnGZ2Y.d.mts.map +1 -0
  865. package/dist/types-ZCnlropY.mjs +69 -0
  866. package/dist/types-ZCnlropY.mjs.map +1 -0
  867. package/dist/types-_Qtyawiy.d.mts +124 -0
  868. package/dist/types-_Qtyawiy.d.mts.map +1 -0
  869. package/dist/types-gC6FAoa_.d.mts +75 -0
  870. package/dist/types-gC6FAoa_.d.mts.map +1 -0
  871. package/dist/types-k--LneIF.mjs +22 -0
  872. package/dist/types-k--LneIF.mjs.map +1 -0
  873. package/dist/upload-attempts-2xdnoKGe.mjs +27 -0
  874. package/dist/upload-attempts-2xdnoKGe.mjs.map +1 -0
  875. package/dist/url-pattern-3m3reMzr.mjs +33 -0
  876. package/dist/url-pattern-3m3reMzr.mjs.map +1 -0
  877. package/dist/user-DroRntkx.mjs +171 -0
  878. package/dist/user-DroRntkx.mjs.map +1 -0
  879. package/dist/utils-CC2oNGV8.mjs +435 -0
  880. package/dist/utils-CC2oNGV8.mjs.map +1 -0
  881. package/dist/validate-BCP9HNDn.mjs +384 -0
  882. package/dist/validate-BCP9HNDn.mjs.map +1 -0
  883. package/dist/validate-CyQ7I3xF.d.mts +410 -0
  884. package/dist/validate-CyQ7I3xF.d.mts.map +1 -0
  885. package/dist/validate-DANX1-Qi.mjs +120 -0
  886. package/dist/validate-DANX1-Qi.mjs.map +1 -0
  887. package/dist/validation-DBy6WF94.mjs +216 -0
  888. package/dist/validation-DBy6WF94.mjs.map +1 -0
  889. package/dist/version-B57E4Uo7.mjs +7 -0
  890. package/dist/version-B57E4Uo7.mjs.map +1 -0
  891. package/dist/widgets-wQ7Xcgq8.mjs +126 -0
  892. package/dist/widgets-wQ7Xcgq8.mjs.map +1 -0
  893. package/dist/wxr-taxonomies-DIHfvEDt.mjs +406 -0
  894. package/dist/wxr-taxonomies-DIHfvEDt.mjs.map +1 -0
  895. package/dist/wxr-taxonomies-DU2ZSiGc.d.mts +34 -0
  896. package/dist/wxr-taxonomies-DU2ZSiGc.d.mts.map +1 -0
  897. package/dist/zod-generator-D_yHIWfs.mjs +329 -0
  898. package/dist/zod-generator-D_yHIWfs.mjs.map +1 -0
  899. package/package.json +39 -12
  900. package/src/api/errors.ts +23 -0
  901. package/src/api/handlers/backup.ts +11 -5
  902. package/src/api/handlers/content.ts +226 -85
  903. package/src/api/handlers/dashboard.ts +31 -1
  904. package/src/api/handlers/entry-lock.ts +206 -0
  905. package/src/api/handlers/index.ts +3 -0
  906. package/src/api/handlers/manifest.ts +5 -6
  907. package/src/api/handlers/marketplace.ts +126 -34
  908. package/src/api/handlers/media-upload.ts +31 -4
  909. package/src/api/handlers/menus.ts +9 -0
  910. package/src/api/handlers/plugin-settings.ts +35 -3
  911. package/src/api/handlers/redirects.ts +221 -138
  912. package/src/api/handlers/registry.ts +75 -29
  913. package/src/api/handlers/revision.ts +16 -27
  914. package/src/api/handlers/seo.ts +10 -1
  915. package/src/api/handlers/snapshot.ts +87 -72
  916. package/src/api/handlers/taxonomies.ts +79 -5
  917. package/src/api/handlers/validation.ts +120 -19
  918. package/src/api/openapi/document.ts +133 -11
  919. package/src/api/schemas/content.ts +32 -12
  920. package/src/api/schemas/entry-lock.ts +57 -0
  921. package/src/api/schemas/index.ts +1 -0
  922. package/src/api/schemas/schema.ts +10 -1
  923. package/src/api/schemas/search.ts +4 -0
  924. package/src/api/types.ts +4 -0
  925. package/src/astro/integration/index.ts +22 -6
  926. package/src/astro/integration/routes.ts +89 -0
  927. package/src/astro/integration/runtime.ts +40 -24
  928. package/src/astro/integration/virtual-modules.ts +4 -0
  929. package/src/astro/integration/vite-config.ts +31 -0
  930. package/src/astro/middleware/auth.ts +25 -4
  931. package/src/astro/middleware/request-context.ts +28 -10
  932. package/src/astro/middleware.ts +33 -1
  933. package/src/astro/prefetch.ts +2 -2
  934. package/src/astro/routes/api/admin/comments/[id]/status.ts +22 -58
  935. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +2 -15
  936. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +2 -25
  937. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +1 -0
  938. package/src/astro/routes/api/admin/plugins/[id]/update.ts +23 -4
  939. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +12 -2
  940. package/src/astro/routes/api/admin/plugins/registry/[id]/uninstall.ts +1 -0
  941. package/src/astro/routes/api/admin/plugins/registry/[id]/update.ts +29 -10
  942. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +8 -2
  943. package/src/astro/routes/api/admin/plugins/registry/install.ts +15 -4
  944. package/src/astro/routes/api/admin/plugins/registry/verify.ts +2 -1
  945. package/src/astro/routes/api/admin/plugins/updates.ts +6 -1
  946. package/src/astro/routes/api/admin/scheduled-policy-rejections/[collection]/[id].ts +34 -0
  947. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +4 -259
  948. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +10 -0
  949. package/src/astro/routes/api/content/[collection]/[id]/lock.ts +133 -0
  950. package/src/astro/routes/api/content/[collection]/[id]/plugin-extensions/[pluginId]/[kind]/[extensionId].ts +29 -0
  951. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +21 -4
  952. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +30 -1
  953. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +15 -1
  954. package/src/astro/routes/api/content/[collection]/[id].ts +27 -2
  955. package/src/astro/routes/api/content/[collection]/index.ts +3 -0
  956. package/src/astro/routes/api/health.ts +23 -0
  957. package/src/astro/routes/api/manifest.ts +14 -4
  958. package/src/astro/routes/api/mcp.ts +2 -1
  959. package/src/astro/routes/api/media/asset/[id]/[filename].ts +50 -0
  960. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +13 -81
  961. package/src/astro/routes/api/redirects/[id].ts +0 -3
  962. package/src/astro/routes/api/redirects/index.ts +0 -2
  963. package/src/astro/routes/api/search/index.ts +1 -0
  964. package/src/astro/routes/api/visual-editing/action-token.ts +36 -0
  965. package/src/astro/routes/api/visual-editing/content/[collection]/[id]/publish.ts +32 -0
  966. package/src/astro/routes/api/visual-editing/toolbar-labels.ts +16 -0
  967. package/src/astro/routes/sitemap-[collection].xml.ts +11 -4
  968. package/src/astro/types.ts +83 -12
  969. package/src/cli/commands/bundle-utils.ts +19 -19
  970. package/src/cli/commands/bundle.ts +9 -6
  971. package/src/cli/commands/content.ts +32 -6
  972. package/src/cli/commands/doctor.ts +25 -0
  973. package/src/cli/commands/export-seed.ts +195 -56
  974. package/src/cli/commands/migrate.ts +71 -23
  975. package/src/cli/commands/publish.ts +2 -2
  976. package/src/client/index.ts +34 -7
  977. package/src/comments/public-submission.ts +246 -0
  978. package/src/comments/service.ts +59 -12
  979. package/src/components/EmDashBodyEnd.astro +4 -1
  980. package/src/components/EmDashBodyStart.astro +4 -1
  981. package/src/components/EmDashHead.astro +36 -16
  982. package/src/components/Gallery.astro +18 -4
  983. package/src/components/InlineEditor.astro +3 -1
  984. package/src/components/InlinePortableTextEditor.tsx +106 -11
  985. package/src/components/PortableText.astro +4 -0
  986. package/src/components/Table.astro +147 -62
  987. package/src/components/widgets/Archives.astro +6 -5
  988. package/src/config/errors.ts +13 -0
  989. package/src/config/secrets.ts +25 -11
  990. package/src/content/converters/portable-text-to-prosemirror.ts +26 -2
  991. package/src/content/converters/prosemirror-to-portable-text.ts +35 -6
  992. package/src/content/converters/types.ts +10 -0
  993. package/src/content/known-fields.ts +44 -0
  994. package/src/database/connection.ts +5 -1
  995. package/src/database/content-datetime.ts +123 -0
  996. package/src/database/datetime-storage.ts +452 -0
  997. package/src/database/dialect-helpers.ts +161 -1
  998. package/src/database/migration-lock.ts +140 -0
  999. package/src/database/migrations/075_entry_edit_locks.ts +44 -0
  1000. package/src/database/migrations/076_collection_nav_group.ts +22 -0
  1001. package/src/database/migrations/077_plugin_storage_revisions.ts +89 -0
  1002. package/src/database/migrations/078_menu_item_translation_groups.ts +15 -0
  1003. package/src/database/migrations/079_datetime_normalization.ts +13 -0
  1004. package/src/database/migrations/080_content_translation_locale_unique.ts +88 -0
  1005. package/src/database/migrations/081_redirect_write_guards.ts +257 -0
  1006. package/src/database/migrations/082_taxonomy_translation_locale_unique.ts +49 -0
  1007. package/src/database/migrations/runner.ts +63 -15
  1008. package/src/database/pg-migration-lock.ts +2 -2
  1009. package/src/database/repositories/byline.ts +9 -0
  1010. package/src/database/repositories/comment.ts +86 -0
  1011. package/src/database/repositories/content.ts +560 -69
  1012. package/src/database/repositories/entry-locks.ts +257 -0
  1013. package/src/database/repositories/media.ts +35 -4
  1014. package/src/database/repositories/menu.ts +1 -0
  1015. package/src/database/repositories/options.ts +113 -7
  1016. package/src/database/repositories/plugin-storage.ts +159 -0
  1017. package/src/database/repositories/redirect.ts +262 -56
  1018. package/src/database/repositories/revision.ts +116 -17
  1019. package/src/database/repositories/taxonomy.ts +42 -0
  1020. package/src/database/repositories/types.ts +5 -0
  1021. package/src/database/types.ts +26 -1
  1022. package/src/datetime-normalization.ts +311 -0
  1023. package/src/db/node-sqlite-compat.ts +3 -2
  1024. package/src/emdash-runtime.ts +1994 -424
  1025. package/src/fields/image.ts +2 -0
  1026. package/src/i18n/config.ts +6 -6
  1027. package/src/i18n/resolve.ts +98 -15
  1028. package/src/index.ts +101 -0
  1029. package/src/loader.ts +49 -4
  1030. package/src/mcp/server.ts +216 -68
  1031. package/src/media/responsive.ts +23 -0
  1032. package/src/media/usage/content-refresh.ts +2 -1
  1033. package/src/media/usage/extractor.ts +50 -15
  1034. package/src/media/usage/types.ts +1 -1
  1035. package/src/menus/index.ts +64 -35
  1036. package/src/migrations/direct-executor.ts +40 -2
  1037. package/src/migrations/index.ts +1 -0
  1038. package/src/migrations/protocol.ts +11 -1
  1039. package/src/page/seo-contributions.ts +78 -1
  1040. package/src/page/seo-panel.ts +47 -0
  1041. package/src/plugin-test-runtime.ts +17 -0
  1042. package/src/plugin-types.ts +128 -21
  1043. package/src/plugins/adapt-sandbox-entry.ts +34 -27
  1044. package/src/plugins/conditional-storage.ts +30 -0
  1045. package/src/plugins/content-access.ts +170 -0
  1046. package/src/plugins/content-policy.ts +74 -0
  1047. package/src/plugins/context.ts +790 -166
  1048. package/src/plugins/cron.ts +18 -10
  1049. package/src/plugins/define-plugin.ts +55 -54
  1050. package/src/plugins/editor-draft.ts +257 -0
  1051. package/src/plugins/hooks.ts +136 -3
  1052. package/src/plugins/host.ts +29 -0
  1053. package/src/plugins/http-route-dispatch.ts +389 -0
  1054. package/src/plugins/http-wire.ts +180 -0
  1055. package/src/plugins/index.ts +97 -3
  1056. package/src/plugins/install-finalization.ts +77 -0
  1057. package/src/plugins/lifecycle.ts +47 -0
  1058. package/src/plugins/manager.ts +5 -2
  1059. package/src/plugins/manifest-schema.ts +314 -58
  1060. package/src/plugins/media.ts +166 -0
  1061. package/src/plugins/request-meta.ts +12 -2
  1062. package/src/plugins/route-wire.ts +304 -0
  1063. package/src/plugins/routes.ts +38 -5
  1064. package/src/plugins/sandbox/index.ts +4 -0
  1065. package/src/plugins/sandbox/proxy.ts +129 -0
  1066. package/src/plugins/sandbox/types.ts +83 -7
  1067. package/src/plugins/secret-redactor.ts +65 -0
  1068. package/src/plugins/settings.ts +303 -0
  1069. package/src/plugins/state.ts +26 -0
  1070. package/src/plugins/storage-query.ts +45 -0
  1071. package/src/plugins/storage-update.ts +96 -0
  1072. package/src/plugins/types.ts +602 -15
  1073. package/src/query.ts +84 -4
  1074. package/src/registry/authoritative-records.ts +26 -0
  1075. package/src/registry/config.ts +182 -31
  1076. package/src/registry/testing.ts +100 -0
  1077. package/src/registry/types.ts +12 -70
  1078. package/src/scheduled-publish.ts +70 -3
  1079. package/src/schema/collection-slugs-cache.ts +157 -0
  1080. package/src/schema/index.ts +1 -0
  1081. package/src/schema/registry.ts +64 -1
  1082. package/src/schema/types.ts +32 -6
  1083. package/src/schema/zod-generator.ts +5 -13
  1084. package/src/search/fts-manager.ts +3 -3
  1085. package/src/search/query.ts +32 -5
  1086. package/src/search/types.ts +8 -0
  1087. package/src/security/ssrf.ts +167 -141
  1088. package/src/seed/apply.ts +41 -3
  1089. package/src/seed/types.ts +13 -3
  1090. package/src/seed/validate.ts +18 -1
  1091. package/src/seo/hreflang.ts +10 -6
  1092. package/src/seo/index.ts +5 -10
  1093. package/src/seo/media-url.ts +26 -0
  1094. package/src/settings/index.ts +53 -11
  1095. package/src/taxonomies/term-counts.ts +22 -5
  1096. package/src/visual-editing/action-token.ts +31 -0
  1097. package/src/visual-editing/toolbar-bootstrap.ts +31 -6
  1098. package/src/visual-editing/toolbar.ts +103 -12
  1099. package/src/widgets/archives.ts +10 -9
  1100. package/src/widgets/index.ts +11 -1
  1101. package/dist/activation-K0kGBWuJ.mjs +0 -382
  1102. package/dist/activation-K0kGBWuJ.mjs.map +0 -1
  1103. package/dist/adapters-BDQwg236.d.mts +0 -171
  1104. package/dist/adapters-BDQwg236.d.mts.map +0 -1
  1105. package/dist/after-CBU2vkoO.mjs +0 -96
  1106. package/dist/after-CBU2vkoO.mjs.map +0 -1
  1107. package/dist/allowed-origins-BGglilcs.mjs +0 -116
  1108. package/dist/allowed-origins-BGglilcs.mjs.map +0 -1
  1109. package/dist/api-Dz6rgoa3.mjs +0 -10040
  1110. package/dist/api-Dz6rgoa3.mjs.map +0 -1
  1111. package/dist/api-tokens-BSsQACNe.mjs +0 -163
  1112. package/dist/api-tokens-BSsQACNe.mjs.map +0 -1
  1113. package/dist/apply-B4SIOxR1.mjs +0 -980
  1114. package/dist/apply-B4SIOxR1.mjs.map +0 -1
  1115. package/dist/authorize-Bkf_UHhD.mjs +0 -40
  1116. package/dist/authorize-Bkf_UHhD.mjs.map +0 -1
  1117. package/dist/backup-rwVAl9NI.mjs +0 -241
  1118. package/dist/backup-rwVAl9NI.mjs.map +0 -1
  1119. package/dist/base64-CvLhUKQv.mjs +0 -63
  1120. package/dist/base64-CvLhUKQv.mjs.map +0 -1
  1121. package/dist/bundle-C8S2Zl6U.mjs +0 -663
  1122. package/dist/bundle-C8S2Zl6U.mjs.map +0 -1
  1123. package/dist/byline-DuhF7NWl.mjs +0 -902
  1124. package/dist/byline-DuhF7NWl.mjs.map +0 -1
  1125. package/dist/byline-fields-BovY4KYM.mjs +0 -123
  1126. package/dist/byline-fields-BovY4KYM.mjs.map +0 -1
  1127. package/dist/byline-registry-Bpu9-MVs.mjs +0 -406
  1128. package/dist/byline-registry-Bpu9-MVs.mjs.map +0 -1
  1129. package/dist/bylines-CJ5nd6Lw.mjs +0 -232
  1130. package/dist/bylines-CJ5nd6Lw.mjs.map +0 -1
  1131. package/dist/bylines-RwBAeOF1.mjs +0 -214
  1132. package/dist/bylines-RwBAeOF1.mjs.map +0 -1
  1133. package/dist/cache-BF5Kvn59.mjs +0 -108
  1134. package/dist/cache-BF5Kvn59.mjs.map +0 -1
  1135. package/dist/capture-triggers-CNW1q3HK.mjs +0 -362
  1136. package/dist/capture-triggers-CNW1q3HK.mjs.map +0 -1
  1137. package/dist/challenge-store-DQN_Yrck.mjs +0 -51
  1138. package/dist/challenge-store-DQN_Yrck.mjs.map +0 -1
  1139. package/dist/chrome-tags-D8fiZG94.mjs +0 -24
  1140. package/dist/chrome-tags-D8fiZG94.mjs.map +0 -1
  1141. package/dist/chunks-C4szrD8o.mjs +0 -25
  1142. package/dist/chunks-C4szrD8o.mjs.map +0 -1
  1143. package/dist/collection-deletion-Bj07NHbj.mjs +0 -436
  1144. package/dist/collection-deletion-Bj07NHbj.mjs.map +0 -1
  1145. package/dist/comment-reaction-BhrvSHml.mjs +0 -85
  1146. package/dist/comment-reaction-BhrvSHml.mjs.map +0 -1
  1147. package/dist/comment-vxOzWbZN.mjs +0 -255
  1148. package/dist/comment-vxOzWbZN.mjs.map +0 -1
  1149. package/dist/comments-Ct4ensnf.mjs +0 -204
  1150. package/dist/comments-Ct4ensnf.mjs.map +0 -1
  1151. package/dist/components-DJbo1z0S.mjs +0 -108
  1152. package/dist/components-DJbo1z0S.mjs.map +0 -1
  1153. package/dist/config-B9eZ340f.d.mts +0 -34
  1154. package/dist/config-B9eZ340f.d.mts.map +0 -1
  1155. package/dist/config-C3EBstHi.mjs +0 -72
  1156. package/dist/config-C3EBstHi.mjs.map +0 -1
  1157. package/dist/config-it29yDLA.mjs +0 -1163
  1158. package/dist/config-it29yDLA.mjs.map +0 -1
  1159. package/dist/content-DjvM0T92.mjs +0 -1725
  1160. package/dist/content-DjvM0T92.mjs.map +0 -1
  1161. package/dist/content-list-query-Btqd4uV2.d.mts +0 -26
  1162. package/dist/content-list-query-Btqd4uV2.d.mts.map +0 -1
  1163. package/dist/content-refresh-Bwp-8yGy.mjs +0 -3588
  1164. package/dist/content-refresh-Bwp-8yGy.mjs.map +0 -1
  1165. package/dist/content-repair-Czdp7jz5.mjs +0 -440
  1166. package/dist/content-repair-Czdp7jz5.mjs.map +0 -1
  1167. package/dist/context-CX6gxOLW.mjs +0 -1068
  1168. package/dist/context-CX6gxOLW.mjs.map +0 -1
  1169. package/dist/cron-C6o7VppJ.mjs +0 -277
  1170. package/dist/cron-C6o7VppJ.mjs.map +0 -1
  1171. package/dist/dashboard-C2kTeeRI.mjs +0 -140
  1172. package/dist/dashboard-C2kTeeRI.mjs.map +0 -1
  1173. package/dist/db-errors-Cl-sKYTm.mjs +0 -55
  1174. package/dist/db-errors-Cl-sKYTm.mjs.map +0 -1
  1175. package/dist/default-Clra32mw.mjs +0 -81
  1176. package/dist/default-Clra32mw.mjs.map +0 -1
  1177. package/dist/deferred-tasks-CvDETTJO.d.mts +0 -9
  1178. package/dist/deferred-tasks-CvDETTJO.d.mts.map +0 -1
  1179. package/dist/dev-typegen-DzOPfsNj.mjs +0 -29
  1180. package/dist/dev-typegen-DzOPfsNj.mjs.map +0 -1
  1181. package/dist/device-flow-D2oCWvLJ.mjs +0 -466
  1182. package/dist/device-flow-D2oCWvLJ.mjs.map +0 -1
  1183. package/dist/dialect-helpers-BSbfaO3F.mjs +0 -202
  1184. package/dist/dialect-helpers-BSbfaO3F.mjs.map +0 -1
  1185. package/dist/direct-executor-Bfa5_ToE.mjs +0 -85
  1186. package/dist/direct-executor-Bfa5_ToE.mjs.map +0 -1
  1187. package/dist/dist-89xCrGqi.mjs +0 -810
  1188. package/dist/dist-89xCrGqi.mjs.map +0 -1
  1189. package/dist/email-console-Dzb6uGYa.mjs +0 -50
  1190. package/dist/email-console-Dzb6uGYa.mjs.map +0 -1
  1191. package/dist/enrich-BX0IDp4z.mjs +0 -194
  1192. package/dist/enrich-BX0IDp4z.mjs.map +0 -1
  1193. package/dist/error-DFrQjppn.mjs +0 -90
  1194. package/dist/error-DFrQjppn.mjs.map +0 -1
  1195. package/dist/errors-DsVoDUjJ.mjs +0 -427
  1196. package/dist/errors-DsVoDUjJ.mjs.map +0 -1
  1197. package/dist/escape-HmHTC1Is.mjs +0 -9
  1198. package/dist/escape-HmHTC1Is.mjs.map +0 -1
  1199. package/dist/field-defs-cache-BM4SIRqH.mjs +0 -77
  1200. package/dist/field-defs-cache-BM4SIRqH.mjs.map +0 -1
  1201. package/dist/focal-point-BeWFo6Xx.mjs +0 -23
  1202. package/dist/focal-point-BeWFo6Xx.mjs.map +0 -1
  1203. package/dist/fts-manager-Do1BlW2r.mjs +0 -413
  1204. package/dist/fts-manager-Do1BlW2r.mjs.map +0 -1
  1205. package/dist/hash-3JJPlHF2.mjs +0 -37
  1206. package/dist/hash-3JJPlHF2.mjs.map +0 -1
  1207. package/dist/hook-result-DCh2WUpe.d.mts +0 -31
  1208. package/dist/hook-result-DCh2WUpe.d.mts.map +0 -1
  1209. package/dist/hreflang-B6hbAuuO.d.mts +0 -48
  1210. package/dist/hreflang-B6hbAuuO.d.mts.map +0 -1
  1211. package/dist/hreflang-DQbPZliA.mjs +0 -102
  1212. package/dist/hreflang-DQbPZliA.mjs.map +0 -1
  1213. package/dist/identity-DksvEazf.mjs +0 -35
  1214. package/dist/identity-DksvEazf.mjs.map +0 -1
  1215. package/dist/import-CJpaBBBT.mjs +0 -2020
  1216. package/dist/import-CJpaBBBT.mjs.map +0 -1
  1217. package/dist/index-BJNMCWmu.d.mts +0 -6562
  1218. package/dist/index-BJNMCWmu.d.mts.map +0 -1
  1219. package/dist/index-Du8K7bWa.d.mts +0 -278
  1220. package/dist/index-Du8K7bWa.d.mts.map +0 -1
  1221. package/dist/init-lock-DJkX6Hto.mjs +0 -56
  1222. package/dist/init-lock-DJkX6Hto.mjs.map +0 -1
  1223. package/dist/load-CawHjTOH.mjs +0 -28
  1224. package/dist/load-CawHjTOH.mjs.map +0 -1
  1225. package/dist/loader-Z8vl7zTq.mjs +0 -966
  1226. package/dist/loader-Z8vl7zTq.mjs.map +0 -1
  1227. package/dist/manifest-builder-COtzuca2.mjs +0 -189
  1228. package/dist/manifest-builder-COtzuca2.mjs.map +0 -1
  1229. package/dist/manifest-schema-BNWwR8Vd.mjs +0 -301
  1230. package/dist/manifest-schema-BNWwR8Vd.mjs.map +0 -1
  1231. package/dist/media-BBm8Wg3f.mjs +0 -318
  1232. package/dist/media-BBm8Wg3f.mjs.map +0 -1
  1233. package/dist/media-BHjus86Y.mjs +0 -464
  1234. package/dist/media-BHjus86Y.mjs.map +0 -1
  1235. package/dist/media-allowlist-CoTBWoDp.mjs +0 -44
  1236. package/dist/media-allowlist-CoTBWoDp.mjs.map +0 -1
  1237. package/dist/media-folders-BGkATs8n.mjs +0 -210
  1238. package/dist/media-folders-BGkATs8n.mjs.map +0 -1
  1239. package/dist/media-upload-BCidR-EY.mjs +0 -147
  1240. package/dist/media-upload-BCidR-EY.mjs.map +0 -1
  1241. package/dist/media-url-DHoPr0hq.mjs +0 -26
  1242. package/dist/media-url-DHoPr0hq.mjs.map +0 -1
  1243. package/dist/media-usage-CdY5jhSR.mjs +0 -1833
  1244. package/dist/media-usage-CdY5jhSR.mjs.map +0 -1
  1245. package/dist/media-usage-activation-BgeMtK5C.mjs +0 -78
  1246. package/dist/media-usage-activation-BgeMtK5C.mjs.map +0 -1
  1247. package/dist/media-usage-work-CnGCdHxx.mjs +0 -152
  1248. package/dist/media-usage-work-CnGCdHxx.mjs.map +0 -1
  1249. package/dist/media-usage-work-D6D9sMCA.mjs +0 -586
  1250. package/dist/media-usage-work-D6D9sMCA.mjs.map +0 -1
  1251. package/dist/media-usage-write-fence-hjeD4u7s.mjs +0 -42
  1252. package/dist/media-usage-write-fence-hjeD4u7s.mjs.map +0 -1
  1253. package/dist/menus-6qSwTNxG.mjs +0 -4010
  1254. package/dist/menus-6qSwTNxG.mjs.map +0 -1
  1255. package/dist/menus-uvPLXMZf.mjs +0 -744
  1256. package/dist/menus-uvPLXMZf.mjs.map +0 -1
  1257. package/dist/mime-CUIG6x84.mjs +0 -36
  1258. package/dist/mime-CUIG6x84.mjs.map +0 -1
  1259. package/dist/mode-DN_WneRx.mjs +0 -23
  1260. package/dist/mode-DN_WneRx.mjs.map +0 -1
  1261. package/dist/node-sqlite-compat-BuJXIeMz.mjs +0 -46
  1262. package/dist/node-sqlite-compat-BuJXIeMz.mjs.map +0 -1
  1263. package/dist/normalize-C2xNjTSG.mjs +0 -191
  1264. package/dist/normalize-C2xNjTSG.mjs.map +0 -1
  1265. package/dist/oauth-authorization-Dj6Kwc21.mjs +0 -274
  1266. package/dist/oauth-authorization-Dj6Kwc21.mjs.map +0 -1
  1267. package/dist/oauth-clients-FD7I0J8m.mjs +0 -266
  1268. package/dist/oauth-clients-FD7I0J8m.mjs.map +0 -1
  1269. package/dist/oauth-state-store-DkmDOmHs.mjs +0 -50
  1270. package/dist/oauth-state-store-DkmDOmHs.mjs.map +0 -1
  1271. package/dist/oauth-user-lookup-CvIwGpYd.mjs +0 -26
  1272. package/dist/oauth-user-lookup-CvIwGpYd.mjs.map +0 -1
  1273. package/dist/object-cache-DVKnyOfy.mjs +0 -534
  1274. package/dist/object-cache-DVKnyOfy.mjs.map +0 -1
  1275. package/dist/options-BHPeIbBd.mjs +0 -117
  1276. package/dist/options-BHPeIbBd.mjs.map +0 -1
  1277. package/dist/options-CYb4y7hM.d.mts +0 -221
  1278. package/dist/options-CYb4y7hM.d.mts.map +0 -1
  1279. package/dist/parse-Ckpg-Qfp.mjs +0 -81
  1280. package/dist/parse-Ckpg-Qfp.mjs.map +0 -1
  1281. package/dist/passkey-config-O_lj3y0k.mjs +0 -46
  1282. package/dist/passkey-config-O_lj3y0k.mjs.map +0 -1
  1283. package/dist/patterns-D9iDO3j8.mjs +0 -145
  1284. package/dist/patterns-D9iDO3j8.mjs.map +0 -1
  1285. package/dist/placeholder-4X9BrRrp.d.mts +0 -306
  1286. package/dist/placeholder-4X9BrRrp.d.mts.map +0 -1
  1287. package/dist/policy-Dhtwntrt.mjs +0 -45
  1288. package/dist/policy-Dhtwntrt.mjs.map +0 -1
  1289. package/dist/portable-text-U85hOafW.mjs +0 -286
  1290. package/dist/portable-text-U85hOafW.mjs.map +0 -1
  1291. package/dist/preview-B_vJLvf8.mjs +0 -107
  1292. package/dist/preview-B_vJLvf8.mjs.map +0 -1
  1293. package/dist/protocol-JdD2YhY9.d.mts +0 -52
  1294. package/dist/protocol-JdD2YhY9.d.mts.map +0 -1
  1295. package/dist/public-url-CarhZJgf.mjs +0 -101
  1296. package/dist/public-url-CarhZJgf.mjs.map +0 -1
  1297. package/dist/query-BZKZcG5b.mjs +0 -881
  1298. package/dist/query-BZKZcG5b.mjs.map +0 -1
  1299. package/dist/query-BndaFxxp.mjs +0 -41
  1300. package/dist/query-BndaFxxp.mjs.map +0 -1
  1301. package/dist/rate-limit-BZmATC6W.mjs +0 -120
  1302. package/dist/rate-limit-BZmATC6W.mjs.map +0 -1
  1303. package/dist/redirect-CZkbozu9.mjs +0 -313
  1304. package/dist/redirect-CZkbozu9.mjs.map +0 -1
  1305. package/dist/redirect-CzqnLxK-.mjs +0 -17
  1306. package/dist/redirect-CzqnLxK-.mjs.map +0 -1
  1307. package/dist/redirects-BLNOWQTn.mjs +0 -1238
  1308. package/dist/redirects-BLNOWQTn.mjs.map +0 -1
  1309. package/dist/redirects-CqzNM0KM.mjs +0 -579
  1310. package/dist/redirects-CqzNM0KM.mjs.map +0 -1
  1311. package/dist/registry-AZAsRsor.mjs +0 -1179
  1312. package/dist/registry-AZAsRsor.mjs.map +0 -1
  1313. package/dist/relation-04DfKRTO.mjs +0 -334
  1314. package/dist/relation-04DfKRTO.mjs.map +0 -1
  1315. package/dist/relations-C4htk5sb.mjs +0 -309
  1316. package/dist/relations-C4htk5sb.mjs.map +0 -1
  1317. package/dist/relations-CcFwDJdJ.mjs +0 -403
  1318. package/dist/relations-CcFwDJdJ.mjs.map +0 -1
  1319. package/dist/relations-DnRK4YIT.d.mts +0 -3354
  1320. package/dist/relations-DnRK4YIT.d.mts.map +0 -1
  1321. package/dist/request-cache-DH9zdCOD.mjs +0 -104
  1322. package/dist/request-cache-DH9zdCOD.mjs.map +0 -1
  1323. package/dist/request-meta-DYxLlnmz.mjs +0 -140
  1324. package/dist/request-meta-DYxLlnmz.mjs.map +0 -1
  1325. package/dist/resolve-prZ3mP7l.mjs +0 -143
  1326. package/dist/resolve-prZ3mP7l.mjs.map +0 -1
  1327. package/dist/runner-BLIDKpmA.mjs +0 -4676
  1328. package/dist/runner-BLIDKpmA.mjs.map +0 -1
  1329. package/dist/runner-YKfIMnaF.d.mts +0 -67
  1330. package/dist/runner-YKfIMnaF.d.mts.map +0 -1
  1331. package/dist/schema-CGnBeLiW.mjs +0 -9
  1332. package/dist/schema-CGnBeLiW.mjs.map +0 -1
  1333. package/dist/schema-DYr-Vd-L.mjs +0 -420
  1334. package/dist/schema-DYr-Vd-L.mjs.map +0 -1
  1335. package/dist/scopes-BhWvBavj.mjs +0 -27
  1336. package/dist/scopes-BhWvBavj.mjs.map +0 -1
  1337. package/dist/search-C-qDk4kx.mjs +0 -439
  1338. package/dist/search-C-qDk4kx.mjs.map +0 -1
  1339. package/dist/secrets-rEtHt-6z.mjs +0 -330
  1340. package/dist/secrets-rEtHt-6z.mjs.map +0 -1
  1341. package/dist/sections-B_nYvlwn.mjs +0 -346
  1342. package/dist/sections-B_nYvlwn.mjs.map +0 -1
  1343. package/dist/seo-926NRIgt.mjs +0 -133
  1344. package/dist/seo-926NRIgt.mjs.map +0 -1
  1345. package/dist/seo-CVWkdv6x.mjs +0 -113
  1346. package/dist/seo-CVWkdv6x.mjs.map +0 -1
  1347. package/dist/service-CJl3ZAYs.mjs +0 -195
  1348. package/dist/service-CJl3ZAYs.mjs.map +0 -1
  1349. package/dist/session-user-BlMgqCHS.mjs +0 -51
  1350. package/dist/session-user-BlMgqCHS.mjs.map +0 -1
  1351. package/dist/settings--wobrkkC.mjs +0 -258
  1352. package/dist/settings--wobrkkC.mjs.map +0 -1
  1353. package/dist/settings-Cwm6o2jY.mjs +0 -51
  1354. package/dist/settings-Cwm6o2jY.mjs.map +0 -1
  1355. package/dist/setup-complete-DQbClIN2.mjs +0 -26
  1356. package/dist/setup-complete-DQbClIN2.mjs.map +0 -1
  1357. package/dist/setup-nonce-DZCFS2sn.mjs +0 -25
  1358. package/dist/setup-nonce-DZCFS2sn.mjs.map +0 -1
  1359. package/dist/single-flight-cache-BPuGPdcN.mjs +0 -104
  1360. package/dist/single-flight-cache-BPuGPdcN.mjs.map +0 -1
  1361. package/dist/site-url-BrxA_E9H.mjs +0 -16
  1362. package/dist/site-url-BrxA_E9H.mjs.map +0 -1
  1363. package/dist/slugify-B994zkXC.mjs +0 -17
  1364. package/dist/slugify-B994zkXC.mjs.map +0 -1
  1365. package/dist/snapshot--OCDTqdb.mjs +0 -216
  1366. package/dist/snapshot--OCDTqdb.mjs.map +0 -1
  1367. package/dist/ssrf-5sIM5jzl.mjs +0 -349
  1368. package/dist/ssrf-5sIM5jzl.mjs.map +0 -1
  1369. package/dist/state-BpFVsJZB.mjs +0 -136
  1370. package/dist/state-BpFVsJZB.mjs.map +0 -1
  1371. package/dist/status-B777-dXz.mjs +0 -30
  1372. package/dist/status-B777-dXz.mjs.map +0 -1
  1373. package/dist/target-BmR0iTwz.mjs +0 -27
  1374. package/dist/target-BmR0iTwz.mjs.map +0 -1
  1375. package/dist/taxonomies-DkAkMupr.mjs +0 -837
  1376. package/dist/taxonomies-DkAkMupr.mjs.map +0 -1
  1377. package/dist/taxonomies-Wfy_Lg12.mjs +0 -698
  1378. package/dist/taxonomies-Wfy_Lg12.mjs.map +0 -1
  1379. package/dist/taxonomy-tJepplLL.mjs +0 -484
  1380. package/dist/taxonomy-tJepplLL.mjs.map +0 -1
  1381. package/dist/tokens-BCO1oruF.mjs +0 -171
  1382. package/dist/tokens-BCO1oruF.mjs.map +0 -1
  1383. package/dist/transaction-0KOpOyMu.mjs +0 -29
  1384. package/dist/transaction-0KOpOyMu.mjs.map +0 -1
  1385. package/dist/transport-Dr3qtwXC.mjs +0 -136
  1386. package/dist/transport-Dr3qtwXC.mjs.map +0 -1
  1387. package/dist/transport-xh6YKW2G.d.mts +0 -42
  1388. package/dist/transport-xh6YKW2G.d.mts.map +0 -1
  1389. package/dist/trusted-proxy-BR5F1Otp.mjs +0 -51
  1390. package/dist/trusted-proxy-BR5F1Otp.mjs.map +0 -1
  1391. package/dist/types-3qNFPlci.mjs +0 -16
  1392. package/dist/types-3qNFPlci.mjs.map +0 -1
  1393. package/dist/types-75KIqzL9.mjs +0 -142
  1394. package/dist/types-75KIqzL9.mjs.map +0 -1
  1395. package/dist/types-B6GrPic4.d.mts +0 -1335
  1396. package/dist/types-B6GrPic4.d.mts.map +0 -1
  1397. package/dist/types-BuHW4xDE.d.mts +0 -259
  1398. package/dist/types-BuHW4xDE.d.mts.map +0 -1
  1399. package/dist/types-C00oyCZp.d.mts +0 -198
  1400. package/dist/types-C00oyCZp.d.mts.map +0 -1
  1401. package/dist/types-CDtkNSFQ.d.mts +0 -75
  1402. package/dist/types-CDtkNSFQ.d.mts.map +0 -1
  1403. package/dist/types-CPeXo1wF.d.mts +0 -124
  1404. package/dist/types-CPeXo1wF.d.mts.map +0 -1
  1405. package/dist/types-CQPWhPxE.mjs +0 -92
  1406. package/dist/types-CQPWhPxE.mjs.map +0 -1
  1407. package/dist/types-CXFkwEGC.d.mts +0 -198
  1408. package/dist/types-CXFkwEGC.d.mts.map +0 -1
  1409. package/dist/types-DM3vioBY.mjs +0 -18
  1410. package/dist/types-DM3vioBY.mjs.map +0 -1
  1411. package/dist/types-DczELMop.d.mts +0 -363
  1412. package/dist/types-DczELMop.d.mts.map +0 -1
  1413. package/dist/types-DfuTR6dL.mjs +0 -3
  1414. package/dist/types-FV23hEoW.d.mts +0 -743
  1415. package/dist/types-FV23hEoW.d.mts.map +0 -1
  1416. package/dist/types-xDMtUFnO.d.mts +0 -257
  1417. package/dist/types-xDMtUFnO.d.mts.map +0 -1
  1418. package/dist/upload-attempts-BsqoQ1fn.mjs +0 -27
  1419. package/dist/upload-attempts-BsqoQ1fn.mjs.map +0 -1
  1420. package/dist/url-pattern-D4c2czpJ.mjs +0 -33
  1421. package/dist/url-pattern-D4c2czpJ.mjs.map +0 -1
  1422. package/dist/user-BTZ40aq6.mjs +0 -171
  1423. package/dist/user-BTZ40aq6.mjs.map +0 -1
  1424. package/dist/utils-BKvNjjhY.mjs +0 -435
  1425. package/dist/utils-BKvNjjhY.mjs.map +0 -1
  1426. package/dist/validate-BpwoDOgt.mjs +0 -120
  1427. package/dist/validate-BpwoDOgt.mjs.map +0 -1
  1428. package/dist/validate-DE_LRwo9.mjs +0 -379
  1429. package/dist/validate-DE_LRwo9.mjs.map +0 -1
  1430. package/dist/validate-DUP27OYD.d.mts +0 -400
  1431. package/dist/validate-DUP27OYD.d.mts.map +0 -1
  1432. package/dist/validation-BMImFJ11.mjs +0 -153
  1433. package/dist/validation-BMImFJ11.mjs.map +0 -1
  1434. package/dist/version-Bii3suaO.mjs +0 -7
  1435. package/dist/version-Bii3suaO.mjs.map +0 -1
  1436. package/dist/widgets-D3gJ10xU.mjs +0 -122
  1437. package/dist/widgets-D3gJ10xU.mjs.map +0 -1
  1438. package/dist/wxr-taxonomies-B2Og_6rU.d.mts +0 -34
  1439. package/dist/wxr-taxonomies-B2Og_6rU.d.mts.map +0 -1
  1440. package/dist/wxr-taxonomies-Cxngsjwo.mjs +0 -406
  1441. package/dist/wxr-taxonomies-Cxngsjwo.mjs.map +0 -1
  1442. package/dist/zod-generator-BuYSQJB7.mjs +0 -331
  1443. package/dist/zod-generator-BuYSQJB7.mjs.map +0 -1
  1444. /package/dist/{api-tokens-BfYts-sq.mjs → api-tokens-DS1VzN7_.mjs} +0 -0
  1445. /package/dist/{ssrf-Dg3cIT-b.mjs → ssrf-BRpTDMV6.mjs} +0 -0
@@ -1,4010 +0,0 @@
1
- import { t as validateIdentifier } from "./validate-BpwoDOgt.mjs";
2
- import { n as cachedQuery, t as CacheNamespace } from "./object-cache-DVKnyOfy.mjs";
3
- import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-C4szrD8o.mjs";
4
- import { o as decodeCursor, s as encodeCursor } from "./types-CQPWhPxE.mjs";
5
- import { t as CommentRepository } from "./comment-vxOzWbZN.mjs";
6
- import { t as OptionsRepository } from "./options-BHPeIbBd.mjs";
7
- import { t as PluginContextFactory } from "./context-CX6gxOLW.mjs";
8
- import { r as requestCached } from "./request-cache-DH9zdCOD.mjs";
9
- import { t as menuTag } from "./chrome-tags-D8fiZG94.mjs";
10
- import { i as resolveLocaleChain, r as resolveLocale } from "./resolve-prZ3mP7l.mjs";
11
- import { s as getDb } from "./loader-Z8vl7zTq.mjs";
12
- import { i as normalizeCapabilities } from "./types-DfuTR6dL.mjs";
13
- import { gt as sanitizeHref } from "./redirects-BLNOWQTn.mjs";
14
- import { t as extractRequestMeta } from "./request-meta-DYxLlnmz.mjs";
15
- import { r as setCronTasksEnabled } from "./cron-C6o7VppJ.mjs";
16
- import { t as MediaUsageActivationWriteBlockedError } from "./media-usage-write-fence-hjeD4u7s.mjs";
17
- import { t as CommentReactionRepository } from "./comment-reaction-BhrvSHml.mjs";
18
- import { sql } from "kysely";
19
- import { ulid } from "ulidx";
20
- import { AsyncLocalStorage } from "node:async_hooks";
21
- import { z } from "astro/zod";
22
- import { z as z$1 } from "zod";
23
- import { Extension, Mark, Node } from "@tiptap/core";
24
-
25
- //#region src/database/repositories/audit.ts
26
- /**
27
- * Audit repository for logging system events
28
- *
29
- * Tracks user actions for security, debugging, and compliance.
30
- * All mutations should create an audit log entry.
31
- */
32
- var AuditRepository = class {
33
- constructor(db) {
34
- this.db = db;
35
- }
36
- /**
37
- * Create an audit log entry
38
- */
39
- async log(input) {
40
- const id = ulid();
41
- const row = {
42
- id,
43
- actor_id: input.actorId ?? null,
44
- actor_ip: input.actorIp ?? null,
45
- action: input.action,
46
- resource_type: input.resourceType ?? null,
47
- resource_id: input.resourceId ?? null,
48
- details: input.details ? JSON.stringify(input.details) : null,
49
- status: input.status ?? null
50
- };
51
- await this.db.insertInto("audit_logs").values(row).execute();
52
- const log = await this.findById(id);
53
- if (!log) throw new Error("Failed to create audit log");
54
- return log;
55
- }
56
- /**
57
- * Find audit log by ID
58
- */
59
- async findById(id) {
60
- const row = await this.db.selectFrom("audit_logs").selectAll().where("id", "=", id).executeTakeFirst();
61
- return row ? this.rowToAuditLog(row) : null;
62
- }
63
- /**
64
- * Query audit logs with filters and cursor-based pagination
65
- */
66
- async findMany(query = {}) {
67
- const limit = Math.min(Math.max(1, query.limit || 50), 100);
68
- let q = this.db.selectFrom("audit_logs").selectAll().orderBy("timestamp", "desc").orderBy("id", "desc").limit(limit + 1);
69
- if (query.actorId) q = q.where("actor_id", "=", query.actorId);
70
- if (query.action) q = q.where("action", "=", query.action);
71
- if (query.resourceType) q = q.where("resource_type", "=", query.resourceType);
72
- if (query.resourceId) q = q.where("resource_id", "=", query.resourceId);
73
- if (query.status) q = q.where("status", "=", query.status);
74
- if (query.since) q = q.where("timestamp", ">=", query.since);
75
- if (query.until) q = q.where("timestamp", "<=", query.until);
76
- if (query.cursor) {
77
- const decoded = decodeCursor(query.cursor);
78
- q = q.where((eb) => eb.or([eb("timestamp", "<", decoded.orderValue), eb.and([eb("timestamp", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
79
- }
80
- const rows = await q.execute();
81
- const items = rows.slice(0, limit).map((row) => this.rowToAuditLog(row));
82
- const result = { items };
83
- if (rows.length > limit && items.length > 0) {
84
- const last = items.at(-1);
85
- result.nextCursor = encodeCursor(last.timestamp, last.id);
86
- }
87
- return result;
88
- }
89
- /**
90
- * Get all logs for a specific resource
91
- */
92
- async findByResource(resourceType, resourceId, options = {}) {
93
- let query = this.db.selectFrom("audit_logs").selectAll().where("resource_type", "=", resourceType).where("resource_id", "=", resourceId).orderBy("timestamp", "desc");
94
- if (options.limit) query = query.limit(options.limit);
95
- return (await query.execute()).map((row) => this.rowToAuditLog(row));
96
- }
97
- /**
98
- * Get all logs for a specific user
99
- */
100
- async findByActor(actorId, options = {}) {
101
- let query = this.db.selectFrom("audit_logs").selectAll().where("actor_id", "=", actorId).orderBy("timestamp", "desc");
102
- if (options.since) query = query.where("timestamp", ">=", options.since);
103
- if (options.limit) query = query.limit(options.limit);
104
- return (await query.execute()).map((row) => this.rowToAuditLog(row));
105
- }
106
- /**
107
- * Count logs matching a query
108
- */
109
- async count(query = {}) {
110
- let q = this.db.selectFrom("audit_logs").select((eb) => eb.fn.count("id").as("count"));
111
- if (query.actorId) q = q.where("actor_id", "=", query.actorId);
112
- if (query.action) q = q.where("action", "=", query.action);
113
- if (query.resourceType) q = q.where("resource_type", "=", query.resourceType);
114
- if (query.resourceId) q = q.where("resource_id", "=", query.resourceId);
115
- if (query.status) q = q.where("status", "=", query.status);
116
- if (query.since) q = q.where("timestamp", ">=", query.since);
117
- if (query.until) q = q.where("timestamp", "<=", query.until);
118
- const result = await q.executeTakeFirst();
119
- return Number(result?.count || 0);
120
- }
121
- /**
122
- * Delete old audit logs (for retention policy)
123
- */
124
- async deleteOlderThan(date) {
125
- const result = await this.db.deleteFrom("audit_logs").where("timestamp", "<", date).executeTakeFirst();
126
- return Number(result.numDeletedRows ?? 0);
127
- }
128
- /**
129
- * Convert database row to AuditLog object
130
- */
131
- rowToAuditLog(row) {
132
- return {
133
- id: row.id,
134
- timestamp: row.timestamp,
135
- actorId: row.actor_id,
136
- actorIp: row.actor_ip,
137
- action: row.action,
138
- resourceType: row.resource_type,
139
- resourceId: row.resource_id,
140
- details: row.details ? JSON.parse(row.details) : null,
141
- status: row.status
142
- };
143
- }
144
- };
145
-
146
- //#endregion
147
- //#region src/fields/image.ts
148
- const mediaSchema = z.object({
149
- id: z.string(),
150
- src: z.string().optional(),
151
- alt: z.string().optional(),
152
- width: z.number().optional(),
153
- height: z.number().optional(),
154
- filename: z.string().optional(),
155
- mimeType: z.string().optional(),
156
- blurhash: z.string().optional(),
157
- dominantColor: z.string().optional(),
158
- provider: z.string().optional(),
159
- previewUrl: z.string().optional(),
160
- meta: z.record(z.string(), z.unknown()).optional()
161
- });
162
- const imageSchema = mediaSchema.extend({ darkVariant: mediaSchema.optional() });
163
- function image(options = {}) {
164
- const validation = options.allowedTypes && options.allowedTypes.length > 0 ? { allowedMimeTypes: [...options.allowedTypes] } : void 0;
165
- return {
166
- type: "image",
167
- columnType: "TEXT",
168
- schema: options.required === false ? imageSchema.optional() : imageSchema,
169
- options,
170
- ui: { widget: "image" },
171
- validation
172
- };
173
- }
174
-
175
- //#endregion
176
- //#region src/fields/file.ts
177
- function file(options = {}) {
178
- const fileObjSchema = z.object({
179
- id: z.string(),
180
- url: z.string().optional(),
181
- src: z.string().optional(),
182
- filename: z.string().optional(),
183
- mimeType: z.string().optional(),
184
- size: z.number().optional(),
185
- provider: z.string().optional(),
186
- meta: z.record(z.string(), z.unknown()).optional()
187
- });
188
- return {
189
- type: "file",
190
- columnType: "TEXT",
191
- schema: options.required ? fileObjSchema : fileObjSchema.optional(),
192
- options,
193
- ui: {
194
- widget: "file",
195
- helpText: options.helpText,
196
- maxSize: options.maxSize
197
- },
198
- validation: options.allowedTypes && options.allowedTypes.length > 0 ? { allowedMimeTypes: [...options.allowedTypes] } : void 0
199
- };
200
- }
201
-
202
- //#endregion
203
- //#region src/fields/reference.ts
204
- /**
205
- * Reference field
206
- * References another content item by ID
207
- */
208
- function reference(collection, options) {
209
- const schema = z.string();
210
- return {
211
- type: "reference",
212
- columnType: "TEXT",
213
- schema: options?.required === false ? schema.optional() : schema,
214
- options: {
215
- ...options,
216
- collection
217
- },
218
- ui: { widget: "reference" }
219
- };
220
- }
221
-
222
- //#endregion
223
- //#region src/fields/portable-text.ts
224
- /**
225
- * Portable Text block schema
226
- */
227
- const portableTextBlockSchema = z.object({
228
- _type: z.string(),
229
- _key: z.string()
230
- }).loose();
231
- /**
232
- * Portable Text field
233
- * Stores structured content in Portable Text format
234
- */
235
- function portableText(options) {
236
- const schema = z.array(portableTextBlockSchema);
237
- return {
238
- type: "portableText",
239
- columnType: "JSON",
240
- schema: options?.required === false ? schema.optional() : schema,
241
- options,
242
- ui: { widget: "portableText" }
243
- };
244
- }
245
-
246
- //#endregion
247
- //#region src/content/converters/gallery.ts
248
- /**
249
- * Normalize an untrusted `images` value into well-formed gallery images.
250
- * Non-object entries and entries without an asset object are dropped.
251
- * Missing `_key`s are filled via `generateKey` when provided (PM → PT);
252
- * left empty otherwise (PT → PM keeps whatever the block carried).
253
- */
254
- function sanitizeGalleryImages(value, generateKey) {
255
- if (!Array.isArray(value)) return [];
256
- const images = [];
257
- for (const entry of value) {
258
- if (!isRecord$4(entry)) continue;
259
- const record = entry;
260
- const asset = record.asset;
261
- if (!isRecord$4(asset)) continue;
262
- const assetRecord = asset;
263
- const image = {
264
- _type: "image",
265
- _key: typeof record._key === "string" && record._key ? record._key : generateKey ? generateKey() : "",
266
- asset: {
267
- _type: "reference",
268
- _ref: typeof assetRecord._ref === "string" ? assetRecord._ref : "",
269
- ...typeof assetRecord.url === "string" && assetRecord.url ? { url: assetRecord.url } : {},
270
- ...typeof assetRecord.provider === "string" && assetRecord.provider ? { provider: assetRecord.provider } : {}
271
- }
272
- };
273
- if (typeof record.alt === "string" && record.alt) image.alt = record.alt;
274
- if (typeof record.caption === "string" && record.caption) image.caption = record.caption;
275
- if (typeof record.width === "number") image.width = record.width;
276
- if (typeof record.height === "number") image.height = record.height;
277
- if (typeof record.focalX === "number") image.focalX = record.focalX;
278
- if (typeof record.focalY === "number") image.focalY = record.focalY;
279
- if (typeof record.blurhash === "string" && record.blurhash) image.blurhash = record.blurhash;
280
- if (typeof record.dominantColor === "string" && record.dominantColor) image.dominantColor = record.dominantColor;
281
- images.push(image);
282
- }
283
- return images;
284
- }
285
- function isRecord$4(value) {
286
- return typeof value === "object" && value !== null && !Array.isArray(value);
287
- }
288
-
289
- //#endregion
290
- //#region src/content/converters/mark-safety.ts
291
- const SUPPORTED_PORTABLE_TEXT_DECORATORS = new Set([
292
- "strong",
293
- "em",
294
- "underline",
295
- "strike-through",
296
- "subscript",
297
- "superscript",
298
- "code"
299
- ]);
300
- const SUPPORTED_PROSEMIRROR_MARKS = new Set([
301
- "bold",
302
- "strong",
303
- "italic",
304
- "em",
305
- "underline",
306
- "strike",
307
- "strikethrough",
308
- "subscript",
309
- "superscript",
310
- "code",
311
- "link",
312
- "emdashPortableTextSpan"
313
- ]);
314
- function isRecord$3(value) {
315
- return typeof value === "object" && value !== null && !Array.isArray(value);
316
- }
317
- function collectPortableTextMarks(value, inheritedMarkDefs, unsupported) {
318
- if (Array.isArray(value)) {
319
- for (const item of value) collectPortableTextMarks(item, inheritedMarkDefs, unsupported);
320
- return;
321
- }
322
- if (!isRecord$3(value)) return;
323
- let markDefs = inheritedMarkDefs;
324
- if (Array.isArray(value.markDefs)) {
325
- const localMarkDefs = new Map(inheritedMarkDefs);
326
- for (const markDef of value.markDefs) if (isRecord$3(markDef) && typeof markDef._key === "string") localMarkDefs.set(markDef._key, markDef);
327
- markDefs = localMarkDefs;
328
- }
329
- if (value._type === "span" && Array.isArray(value.marks)) for (const mark of value.marks) {
330
- if (typeof mark !== "string" || SUPPORTED_PORTABLE_TEXT_DECORATORS.has(mark)) continue;
331
- const markDef = markDefs.get(mark);
332
- if (markDef?._type === "link") continue;
333
- unsupported.add(typeof markDef?._type === "string" ? markDef._type : mark);
334
- }
335
- for (const [key, child] of Object.entries(value)) if (key !== "markDefs") collectPortableTextMarks(child, markDefs, unsupported);
336
- }
337
- function collectProseMirrorMarks(node, unsupported) {
338
- for (const mark of node.marks ?? []) if (!SUPPORTED_PROSEMIRROR_MARKS.has(mark.type)) unsupported.add(mark.type);
339
- for (const child of node.content ?? []) collectProseMirrorMarks(child, unsupported);
340
- }
341
- var UnsupportedPortableTextMarksError = class extends Error {
342
- marks;
343
- constructor(marks) {
344
- const sortedMarks = [...new Set(marks)].toSorted();
345
- super(`Unsupported Portable Text marks: ${sortedMarks.join(", ")}`);
346
- this.name = "UnsupportedPortableTextMarksError";
347
- this.marks = sortedMarks;
348
- }
349
- };
350
- function findUnsupportedPortableTextMarks(blocks) {
351
- const unsupported = /* @__PURE__ */ new Set();
352
- for (const block of blocks) if (block._type === "block" || block._type === "table") collectPortableTextMarks(block, /* @__PURE__ */ new Map(), unsupported);
353
- return [...unsupported].toSorted();
354
- }
355
- function assertPortableTextMarksSupported(blocks) {
356
- const unsupported = findUnsupportedPortableTextMarks(blocks);
357
- if (unsupported.length > 0) throw new UnsupportedPortableTextMarksError(unsupported);
358
- }
359
- function assertProseMirrorMarksSupported(document) {
360
- const unsupported = /* @__PURE__ */ new Set();
361
- for (const node of document.content) collectProseMirrorMarks(node, unsupported);
362
- if (unsupported.size > 0) throw new UnsupportedPortableTextMarksError(unsupported);
363
- }
364
-
365
- //#endregion
366
- //#region src/content/converters/numbered-list.ts
367
- const MAX_ORDERED_LIST_START = 2147483647;
368
- function normalizeListId(value) {
369
- if (typeof value !== "string") return void 0;
370
- const normalized = value.trim();
371
- return normalized.length > 0 && normalized.length <= 128 ? normalized : void 0;
372
- }
373
- function normalizeListStart(value) {
374
- return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_ORDERED_LIST_START ? value : void 0;
375
- }
376
- function deriveLegacyListId(seed) {
377
- const readable = `legacy:${seed}`;
378
- if (readable.length <= 128) return readable;
379
- let hash = 2166136261;
380
- for (let i = 0; i < seed.length; i++) {
381
- hash ^= seed.charCodeAt(i);
382
- hash = Math.imul(hash, 16777619);
383
- }
384
- return `legacy:${seed.slice(0, 96)}:${(hash >>> 0).toString(36)}:${seed.length.toString(36)}`;
385
- }
386
- function readOrderedListMetadata(attrs, fallbackId) {
387
- return {
388
- listId: normalizeListId(attrs?.listId) ?? deriveLegacyListId(fallbackId),
389
- listStart: normalizeListStart(attrs?.listStart) ?? normalizeListStart(attrs?.start) ?? 1
390
- };
391
- }
392
- function collectProseMirrorOrderedLists(doc) {
393
- const lists = [];
394
- const visit = (node, path, depth, context) => {
395
- if (node.type === "orderedList") lists.push({
396
- node,
397
- path,
398
- depth,
399
- context
400
- });
401
- for (const [index, child] of (node.content ?? []).entries()) {
402
- const childPath = `${path}:${index}`;
403
- visit(child, childPath, depth + 1, child.type === "listItem" ? `listItem:${childPath}` : context);
404
- }
405
- };
406
- for (const [index, node] of doc.content.entries()) visit(node, `root:${index}`, 0, "root");
407
- return lists;
408
- }
409
- function cloneProseMirrorNode(node) {
410
- return {
411
- ...node,
412
- attrs: node.attrs ? { ...node.attrs } : void 0,
413
- content: node.content?.map(cloneProseMirrorNode),
414
- marks: node.marks?.map((mark) => ({
415
- ...mark,
416
- attrs: mark.attrs ? { ...mark.attrs } : void 0
417
- }))
418
- };
419
- }
420
- function createRepairId(sourceId, scope, attempt) {
421
- const base = deriveLegacyListId(`repair:${sourceId}:${scope}`);
422
- const suffix = `:${attempt.toString(36)}`;
423
- return `${base.slice(0, 128 - suffix.length)}${suffix}`;
424
- }
425
- function normalizeProseMirrorOrderedListJson(doc) {
426
- const normalized = {
427
- ...doc,
428
- content: doc.content.map(cloneProseMirrorNode)
429
- };
430
- const canonicalBySourceScope = /* @__PURE__ */ new Map();
431
- const assignedCanonicalIds = /* @__PURE__ */ new Set();
432
- const lists = collectProseMirrorOrderedLists(normalized).map((list) => {
433
- const sourceId = normalizeListId(list.node.attrs?.listId) ?? deriveLegacyListId(`pm-json:${list.path}:${list.depth}:${list.context}`);
434
- const scope = JSON.stringify([list.depth, list.context]);
435
- const sourceScope = JSON.stringify([sourceId, scope]);
436
- let listId = canonicalBySourceScope.get(sourceScope);
437
- if (!listId) {
438
- if (!assignedCanonicalIds.has(sourceId)) listId = sourceId;
439
- else {
440
- let attempt = 0;
441
- do
442
- listId = createRepairId(sourceId, scope, attempt++);
443
- while (assignedCanonicalIds.has(listId));
444
- }
445
- canonicalBySourceScope.set(sourceScope, listId);
446
- assignedCanonicalIds.add(listId);
447
- }
448
- return {
449
- ...list,
450
- listId,
451
- scopeKey: JSON.stringify([
452
- listId,
453
- list.depth,
454
- list.context
455
- ])
456
- };
457
- });
458
- const bases = /* @__PURE__ */ new Map();
459
- for (const list of lists) {
460
- const listStart = normalizeListStart(list.node.attrs?.listStart);
461
- if (listStart !== void 0 && !bases.has(list.scopeKey)) bases.set(list.scopeKey, listStart);
462
- }
463
- for (const list of lists) if (!bases.has(list.scopeKey)) bases.set(list.scopeKey, normalizeListStart(list.node.attrs?.start) ?? 1);
464
- const counts = /* @__PURE__ */ new Map();
465
- for (const list of lists) {
466
- const listStart = bases.get(list.scopeKey);
467
- const count = counts.get(list.scopeKey) ?? 0;
468
- const start = normalizeListStart(listStart + count) ?? 1;
469
- const directItemCount = list.node.content?.filter((node) => node.type === "listItem").length ?? 0;
470
- counts.set(list.scopeKey, count + directItemCount);
471
- list.node.attrs = {
472
- ...list.node.attrs,
473
- listId: list.listId,
474
- listStart,
475
- start
476
- };
477
- }
478
- return normalized;
479
- }
480
-
481
- //#endregion
482
- //#region src/content/converters/portable-text-identity.ts
483
- const PORTABLE_TEXT_BLOCK_NODE = "emdashPortableTextBlock";
484
- const PORTABLE_TEXT_BLOCK_ATTR = "emdashPortableTextBlock";
485
- const PORTABLE_TEXT_KEY_ATTR = "emdashPortableTextKey";
486
- const PORTABLE_TEXT_SPAN_MARK = "emdashPortableTextSpan";
487
- function isRecord$2(value) {
488
- return typeof value === "object" && value !== null && !Array.isArray(value);
489
- }
490
- function attrsWithPortableTextKey(attrs, key) {
491
- return {
492
- ...attrs,
493
- [PORTABLE_TEXT_KEY_ATTR]: key
494
- };
495
- }
496
- function portableTextKeyFromAttrs(attrs) {
497
- const key = attrs?.[PORTABLE_TEXT_KEY_ATTR];
498
- return typeof key === "string" && key ? key : void 0;
499
- }
500
- function portableTextSpanKeyFromMarks(marks) {
501
- for (const mark of marks ?? []) {
502
- if (!isRecord$2(mark) || mark.type !== PORTABLE_TEXT_SPAN_MARK || !isRecord$2(mark.attrs)) continue;
503
- const key = mark.attrs.key;
504
- if (typeof key === "string" && key) return key;
505
- }
506
- }
507
- function portableTextMarkDefsFromMarks(marks) {
508
- const identity = (marks ?? []).find((mark) => isRecord$2(mark) && mark.type === PORTABLE_TEXT_SPAN_MARK && isRecord$2(mark.attrs));
509
- if (!isRecord$2(identity) || !isRecord$2(identity.attrs) || !Array.isArray(identity.attrs.markDefs)) return [];
510
- return identity.attrs.markDefs.filter((markDef) => isRecord$2(markDef) && typeof markDef._type === "string" && typeof markDef._key === "string");
511
- }
512
- function portableTextBlockFromAttrs(attrs) {
513
- const block = attrs?.[PORTABLE_TEXT_BLOCK_ATTR];
514
- if (!isRecord$2(block) || typeof block._type !== "string" || typeof block._key !== "string") return;
515
- return {
516
- ...block,
517
- _type: block._type,
518
- _key: block._key
519
- };
520
- }
521
- const PortableTextIdentityExtension = Extension.create({
522
- name: "emdashPortableTextIdentity",
523
- addGlobalAttributes() {
524
- const hiddenAttribute = {
525
- default: null,
526
- rendered: false
527
- };
528
- return [{
529
- types: [
530
- "paragraph",
531
- "heading",
532
- "blockquote",
533
- "codeBlock",
534
- "htmlBlock",
535
- "image",
536
- "horizontalRule",
537
- "gallery",
538
- PORTABLE_TEXT_BLOCK_NODE
539
- ],
540
- attributes: { [PORTABLE_TEXT_KEY_ATTR]: hiddenAttribute }
541
- }];
542
- }
543
- });
544
- const PortableTextSpanIdentity = Mark.create({
545
- name: PORTABLE_TEXT_SPAN_MARK,
546
- inclusive: false,
547
- spanning: false,
548
- addAttributes() {
549
- return {
550
- key: {
551
- default: null,
552
- rendered: false
553
- },
554
- markDefs: {
555
- default: [],
556
- rendered: false
557
- }
558
- };
559
- },
560
- parseHTML() {
561
- return [];
562
- },
563
- renderHTML() {
564
- return ["span", 0];
565
- }
566
- });
567
- const PortableTextOpaqueBlock = Node.create({
568
- name: PORTABLE_TEXT_BLOCK_NODE,
569
- group: "block",
570
- atom: true,
571
- selectable: true,
572
- draggable: true,
573
- addAttributes() {
574
- return { [PORTABLE_TEXT_BLOCK_ATTR]: {
575
- default: null,
576
- rendered: false
577
- } };
578
- },
579
- parseHTML() {
580
- return [{ tag: "div[data-emdash-portable-text-block]" }];
581
- },
582
- renderHTML({ node }) {
583
- const blockType = portableTextBlockFromAttrs(node.attrs)?._type ?? "unknown";
584
- return [
585
- "div",
586
- {
587
- "data-emdash-portable-text-block": blockType,
588
- contenteditable: "false"
589
- },
590
- `[Unknown block type: ${blockType}]`
591
- ];
592
- }
593
- });
594
- const portableTextIdentityExtensions = [
595
- PortableTextIdentityExtension,
596
- PortableTextSpanIdentity,
597
- PortableTextOpaqueBlock
598
- ];
599
-
600
- //#endregion
601
- //#region src/content/converters/prosemirror-to-portable-text.ts
602
- /**
603
- * ProseMirror to Portable Text Converter
604
- *
605
- * Converts TipTap's ProseMirror JSON format to Portable Text for storage.
606
- */
607
- /**
608
- * Generate a unique key for Portable Text blocks
609
- */
610
- function generateKey() {
611
- return Math.random().toString(36).substring(2, 11);
612
- }
613
- /**
614
- * Convert ProseMirror document to Portable Text
615
- */
616
- function prosemirrorToPortableText(doc) {
617
- if (!doc || doc.type !== "doc" || !doc.content) return [];
618
- assertProseMirrorMarksSupported(doc);
619
- const blocks = [];
620
- const usedBlockKeys = /* @__PURE__ */ new Set();
621
- for (const [i, node] of doc.content.entries()) {
622
- if (i === doc.content.length - 1 && isUnkeyedEmptyParagraph(node)) continue;
623
- const converted = convertNode(node, `root:${i}`);
624
- for (const block of converted ? Array.isArray(converted) ? converted : [converted] : []) {
625
- let key = block._key;
626
- if (usedBlockKeys.has(key)) do
627
- key = generateKey();
628
- while (usedBlockKeys.has(key));
629
- usedBlockKeys.add(key);
630
- blocks.push(key === block._key ? block : {
631
- ...block,
632
- _key: key
633
- });
634
- }
635
- }
636
- return blocks;
637
- }
638
- function isUnkeyedEmptyParagraph(node) {
639
- return node.type === "paragraph" && (node.content?.length ?? 0) === 0 && portableTextKeyFromAttrs(node.attrs) === void 0;
640
- }
641
- /**
642
- * Convert a single ProseMirror node to Portable Text block(s)
643
- */
644
- function convertNode(node, path) {
645
- switch (node.type) {
646
- case PORTABLE_TEXT_BLOCK_NODE: return portableTextBlockFromAttrs(node.attrs) ?? null;
647
- case "paragraph": return convertParagraph(node);
648
- case "heading": return convertHeading(node);
649
- case "bulletList": return convertList$1(node, "bullet", path);
650
- case "orderedList": return convertList$1(node, "number", path);
651
- case "blockquote": return convertBlockquote(node);
652
- case "codeBlock": return convertCodeBlock$1(node);
653
- case "htmlBlock": return convertHtmlBlock(node);
654
- case "image": return convertImage$1(node);
655
- case "gallery": return convertGallery(node);
656
- case "horizontalRule": return {
657
- _type: "break",
658
- _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
659
- style: "lineBreak"
660
- };
661
- default: return {
662
- _type: node.type,
663
- _key: generateKey(),
664
- ...node.attrs,
665
- _pmContent: node.content
666
- };
667
- }
668
- }
669
- /**
670
- * Convert paragraph to Portable Text block
671
- */
672
- function convertParagraph(node) {
673
- const { children, markDefs } = convertInlineContent(node.content || []);
674
- if (children.length === 0) return null;
675
- const ta = node.attrs?.textAlign;
676
- const textAlign = ta === "center" || ta === "right" || ta === "justify" ? ta : void 0;
677
- return {
678
- _type: "block",
679
- _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
680
- style: "normal",
681
- children,
682
- ...markDefs.length > 0 ? { markDefs } : {},
683
- ...textAlign ? { textAlign } : {}
684
- };
685
- }
686
- /** Map heading level number to Portable Text style */
687
- function headingLevelToStyle(level) {
688
- switch (level) {
689
- case 1: return "h1";
690
- case 2: return "h2";
691
- case 3: return "h3";
692
- case 4: return "h4";
693
- case 5: return "h5";
694
- case 6: return "h6";
695
- default: return "h1";
696
- }
697
- }
698
- /**
699
- * Convert heading to Portable Text block
700
- */
701
- function convertHeading(node) {
702
- const { children, markDefs } = convertInlineContent(node.content || []);
703
- const style = headingLevelToStyle(typeof node.attrs?.level === "number" ? node.attrs.level : 1);
704
- if (children.length === 0) return null;
705
- const ta = node.attrs?.textAlign;
706
- const textAlign = ta === "center" || ta === "right" || ta === "justify" ? ta : void 0;
707
- return {
708
- _type: "block",
709
- _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
710
- style,
711
- children,
712
- markDefs: markDefs.length > 0 ? markDefs : void 0,
713
- ...textAlign ? { textAlign } : {}
714
- };
715
- }
716
- /**
717
- * Convert list to Portable Text blocks
718
- */
719
- function convertList$1(node, listItem, path) {
720
- const blocks = [];
721
- const metadata = listItem === "number" ? readOrderedListMetadata(node.attrs, path) : void 0;
722
- for (const [i, item] of (node.content ?? []).entries()) if (item.type === "listItem") {
723
- const itemBlocks = convertListItem$1(item, listItem, 1, `${path}:${i}`, metadata);
724
- blocks.push(...itemBlocks);
725
- }
726
- return blocks;
727
- }
728
- /**
729
- * Convert list item to Portable Text blocks
730
- */
731
- function convertListItem$1(item, listItem, level, path, metadata) {
732
- const blocks = [];
733
- for (const [i, child] of (item.content ?? []).entries()) if (child.type === "paragraph") {
734
- const { children, markDefs } = convertInlineContent(child.content || []);
735
- if (children.length > 0) blocks.push({
736
- _type: "block",
737
- _key: portableTextKeyFromAttrs(child.attrs) ?? generateKey(),
738
- style: "normal",
739
- listItem,
740
- level,
741
- ...metadata,
742
- children,
743
- markDefs: markDefs.length > 0 ? markDefs : void 0
744
- });
745
- } else if (child.type === "bulletList") blocks.push(...convertListItemNested(child, "bullet", level + 1, `${path}:${i}`));
746
- else if (child.type === "orderedList") blocks.push(...convertListItemNested(child, "number", level + 1, `${path}:${i}`));
747
- return blocks;
748
- }
749
- /**
750
- * Convert nested list
751
- */
752
- function convertListItemNested(node, listItem, level, path) {
753
- const blocks = [];
754
- const metadata = listItem === "number" ? readOrderedListMetadata(node.attrs, path) : void 0;
755
- for (const [i, item] of (node.content ?? []).entries()) if (item.type === "listItem") blocks.push(...convertListItem$1(item, listItem, level, `${path}:${i}`, metadata));
756
- return blocks;
757
- }
758
- /**
759
- * Convert blockquote to Portable Text blocks
760
- */
761
- function convertBlockquote(node) {
762
- const blocks = [];
763
- for (const child of node.content || []) if (child.type === "paragraph") {
764
- const { children, markDefs } = convertInlineContent(child.content || []);
765
- if (children.length > 0) blocks.push({
766
- _type: "block",
767
- _key: portableTextKeyFromAttrs(child.attrs) ?? portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
768
- style: "blockquote",
769
- children,
770
- markDefs: markDefs.length > 0 ? markDefs : void 0
771
- });
772
- }
773
- return blocks.length === 1 ? blocks[0] : blocks.length > 0 ? blocks : null;
774
- }
775
- /**
776
- * Convert code block to Portable Text
777
- */
778
- function convertCodeBlock$1(node) {
779
- const code = node.content?.map((n) => n.text || "").join("") || "";
780
- const language = typeof node.attrs?.language === "string" ? node.attrs.language : void 0;
781
- return {
782
- _type: "code",
783
- _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
784
- code,
785
- language: language || void 0
786
- };
787
- }
788
- /**
789
- * Convert HTML block to Portable Text
790
- */
791
- function convertHtmlBlock(node) {
792
- const rawHtml = node.attrs?.html;
793
- return {
794
- _type: "htmlBlock",
795
- _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
796
- html: typeof rawHtml === "string" ? rawHtml : ""
797
- };
798
- }
799
- /**
800
- * Convert image to Portable Text
801
- */
802
- function convertImage$1(node) {
803
- const attrs = node.attrs;
804
- const provider = typeof attrs?.provider === "string" ? attrs.provider : void 0;
805
- const mediaId = typeof attrs?.mediaId === "string" ? attrs.mediaId : void 0;
806
- const src = typeof attrs?.src === "string" ? attrs.src : "";
807
- const alt = typeof attrs?.alt === "string" ? attrs.alt : void 0;
808
- const title = typeof attrs?.title === "string" ? attrs.title : void 0;
809
- const width = typeof attrs?.width === "number" ? attrs.width : void 0;
810
- const height = typeof attrs?.height === "number" ? attrs.height : void 0;
811
- const displayWidth = typeof attrs?.displayWidth === "number" ? attrs.displayWidth : void 0;
812
- const displayHeight = typeof attrs?.displayHeight === "number" ? attrs.displayHeight : void 0;
813
- const alignment = attrs?.alignment;
814
- return {
815
- _type: "image",
816
- _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
817
- asset: {
818
- _ref: mediaId || src || "",
819
- url: src || "",
820
- provider: provider && provider !== "local" ? provider : void 0
821
- },
822
- alt: alt || void 0,
823
- caption: title || void 0,
824
- width: width || void 0,
825
- height: height || void 0,
826
- displayWidth: displayWidth || void 0,
827
- displayHeight: displayHeight || void 0,
828
- alignment: alignment === "left" || alignment === "center" || alignment === "right" || alignment === "wide" || alignment === "full" ? alignment : void 0
829
- };
830
- }
831
- /**
832
- * Convert gallery node to Portable Text
833
- */
834
- function convertGallery(node) {
835
- const columns = node.attrs?.columns;
836
- return {
837
- _type: "gallery",
838
- _key: portableTextKeyFromAttrs(node.attrs) ?? generateKey(),
839
- images: sanitizeGalleryImages(node.attrs?.images, generateKey),
840
- ...typeof columns === "number" ? { columns } : {}
841
- };
842
- }
843
- /**
844
- * Convert inline content (text nodes with marks) to Portable Text spans
845
- */
846
- function convertInlineContent(nodes) {
847
- const children = [];
848
- const markDefs = [];
849
- const markDefMap = /* @__PURE__ */ new Map();
850
- const usedSpanKeys = /* @__PURE__ */ new Set();
851
- const claimSpanKey = (preferred) => {
852
- if (preferred && !usedSpanKeys.has(preferred)) {
853
- usedSpanKeys.add(preferred);
854
- return preferred;
855
- }
856
- let key;
857
- do
858
- key = generateKey();
859
- while (usedSpanKeys.has(key));
860
- usedSpanKeys.add(key);
861
- return key;
862
- };
863
- for (const node of nodes) if (node.type === "text" && node.text) {
864
- const marks = [];
865
- const originalMarkDefs = portableTextMarkDefsFromMarks(node.marks);
866
- for (const mark of node.marks || []) {
867
- const markType = convertMark(mark, markDefs, markDefMap, originalMarkDefs);
868
- if (markType) marks.push(markType);
869
- }
870
- const preferredKey = portableTextSpanKeyFromMarks(node.marks) ?? portableTextKeyFromAttrs(node.attrs);
871
- const normalizedMarks = marks.length > 0 ? marks : void 0;
872
- const previous = children.at(-1);
873
- if (preferredKey && previous?._key === preferredKey && JSON.stringify(previous.marks) === JSON.stringify(normalizedMarks)) {
874
- previous.text += node.text;
875
- continue;
876
- }
877
- children.push({
878
- _type: "span",
879
- _key: claimSpanKey(preferredKey),
880
- text: node.text,
881
- marks: normalizedMarks
882
- });
883
- } else if (node.type === "hardBreak") if (children.length > 0) {
884
- const lastChild = children.at(-1);
885
- lastChild.text += "\n";
886
- } else children.push({
887
- _type: "span",
888
- _key: claimSpanKey(portableTextSpanKeyFromMarks(node.marks) ?? portableTextKeyFromAttrs(node.attrs)),
889
- text: "\n"
890
- });
891
- if (children.length === 0) children.push({
892
- _type: "span",
893
- _key: claimSpanKey(),
894
- text: ""
895
- });
896
- return {
897
- children,
898
- markDefs
899
- };
900
- }
901
- /**
902
- * Convert a ProseMirror mark to Portable Text mark
903
- */
904
- function convertMark(mark, markDefs, markDefMap, originalMarkDefs) {
905
- switch (mark.type) {
906
- case "bold":
907
- case "strong": return "strong";
908
- case "italic":
909
- case "em": return "em";
910
- case "underline": return "underline";
911
- case "strike":
912
- case "strikethrough": return "strike-through";
913
- case "subscript": return "subscript";
914
- case "superscript": return "superscript";
915
- case "code": return "code";
916
- case PORTABLE_TEXT_SPAN_MARK: return null;
917
- case "link": {
918
- const href = (typeof mark.attrs?.href === "string" ? mark.attrs.href : "") || "";
919
- const originalMarkDef = originalMarkDefs.find((markDef) => markDef._type === "link");
920
- const mapKey = originalMarkDef ? `key:${originalMarkDef._key}` : `href:${href}`;
921
- if (markDefMap.has(mapKey)) return markDefMap.get(mapKey);
922
- const key = originalMarkDef?._key || generateKey();
923
- const blank = mark.attrs?.target === "_blank";
924
- markDefs.push({
925
- ...originalMarkDef,
926
- _type: "link",
927
- _key: key,
928
- href,
929
- ...blank || originalMarkDef && Object.hasOwn(originalMarkDef, "blank") ? { blank } : {}
930
- });
931
- markDefMap.set(mapKey, key);
932
- return key;
933
- }
934
- default: throw new UnsupportedPortableTextMarksError([mark.type]);
935
- }
936
- }
937
-
938
- //#endregion
939
- //#region src/content/converters/portable-text-to-prosemirror.ts
940
- /**
941
- * Portable Text to ProseMirror Converter
942
- *
943
- * Converts Portable Text to TipTap's ProseMirror JSON format for editing.
944
- */
945
- /** Convert Portable Text to a ProseMirror document. */
946
- function portableTextToProsemirror(blocks, options = {}) {
947
- if (!blocks || blocks.length === 0) return {
948
- type: "doc",
949
- content: [{ type: "paragraph" }]
950
- };
951
- assertPortableTextMarksSupported(blocks);
952
- const preserveIdentity = options.preserveIdentity === true;
953
- const content = [];
954
- let i = 0;
955
- while (i < blocks.length) {
956
- const block = blocks[i];
957
- if (isTextBlock(block) && block.listItem) {
958
- const listBlocks = [];
959
- const listType = block.listItem;
960
- const runStart = i;
961
- const rootId = listType === "number" ? normalizeListId(block.listId) : void 0;
962
- while (i < blocks.length) {
963
- const current = blocks[i];
964
- if (!isTextBlock(current) || !current.listItem) break;
965
- const level = current.level || 1;
966
- const currentId = current.listItem === "number" ? normalizeListId(current.listId) : void 0;
967
- const sameRootIdentity = listType !== "number" || level > 1 || (rootId ? currentId === rootId : currentId === void 0);
968
- if (level > 1 || current.listItem === listType && sameRootIdentity) {
969
- listBlocks.push(current);
970
- i++;
971
- } else break;
972
- }
973
- content.push(convertList(listBlocks, listType, `root:${runStart}`, preserveIdentity));
974
- } else if (isTextBlock(block) && block.style === "blockquote") {
975
- const quoteBlocks = [];
976
- while (i < blocks.length) {
977
- const current = blocks[i];
978
- if (!isTextBlock(current) || current.style !== "blockquote" || current.listItem !== void 0) break;
979
- quoteBlocks.push(current);
980
- i++;
981
- }
982
- content.push({
983
- type: "blockquote",
984
- content: quoteBlocks.map((quoteBlock) => {
985
- const paragraph = convertSpans(quoteBlock.children, quoteBlock.markDefs || [], preserveIdentity);
986
- return {
987
- type: "paragraph",
988
- attrs: identityAttrs(void 0, quoteBlock._key, preserveIdentity),
989
- content: paragraph.length > 0 ? paragraph : void 0
990
- };
991
- })
992
- });
993
- } else {
994
- const converted = convertBlock(block, preserveIdentity);
995
- if (converted) content.push(converted);
996
- i++;
997
- }
998
- }
999
- return normalizeProseMirrorOrderedListJson({
1000
- type: "doc",
1001
- content: content.length > 0 ? content : [{ type: "paragraph" }]
1002
- });
1003
- }
1004
- function identityAttrs(attrs, key, preserveIdentity) {
1005
- return preserveIdentity ? attrsWithPortableTextKey(attrs, key) : attrs;
1006
- }
1007
- function getListMetadata(item, fallbackSeed) {
1008
- const listId = normalizeListId(item.listId) ?? deriveLegacyListId(fallbackSeed);
1009
- const listStart = normalizeListStart(item.listStart);
1010
- return {
1011
- listId,
1012
- ...listStart === void 0 ? {} : { listStart }
1013
- };
1014
- }
1015
- function belongsToNestedGroup(item, minLevel, parentListType, anchorType, anchorId) {
1016
- if ((item.level || 2) > minLevel) return true;
1017
- if ((item.listItem || parentListType) !== anchorType) return false;
1018
- if (anchorType !== "number") return true;
1019
- const itemId = normalizeListId(item.listId);
1020
- return anchorId ? itemId === anchorId : itemId === void 0;
1021
- }
1022
- /**
1023
- * Type guard for text blocks
1024
- */
1025
- function isTextBlock(block) {
1026
- return block._type === "block";
1027
- }
1028
- /**
1029
- * Type guard for image blocks.
1030
- * Checks both `_type` and that `asset` is a valid object — image blocks
1031
- * without an `asset` wrapper (e.g. `{ _type: "image", url: "..." }`) are
1032
- * malformed and should not be cast to `PortableTextImageBlock`.
1033
- */
1034
- function isImageBlock(block) {
1035
- return block._type === "image" && "asset" in block && typeof block.asset === "object" && block.asset !== null;
1036
- }
1037
- /**
1038
- * Type guard for gallery blocks that carry a usable `images` array.
1039
- * Galleries without one are handled separately in `convertBlock`.
1040
- */
1041
- function isGalleryBlock(block) {
1042
- return block._type === "gallery" && "images" in block && Array.isArray(block.images);
1043
- }
1044
- /**
1045
- * Type guard for code blocks
1046
- */
1047
- function isCodeBlock(block) {
1048
- return block._type === "code";
1049
- }
1050
- /**
1051
- * Convert a single Portable Text block to ProseMirror node
1052
- */
1053
- function convertBlock(block, preserveIdentity) {
1054
- if (isTextBlock(block)) return convertTextBlock(block, preserveIdentity);
1055
- if (isImageBlock(block)) return convertImage(block, preserveIdentity);
1056
- if (block._type === "image") return convertMalformedImage(block, preserveIdentity);
1057
- if (isGalleryBlock(block)) return {
1058
- type: "gallery",
1059
- attrs: identityAttrs({
1060
- images: sanitizeGalleryImages(block.images),
1061
- columns: typeof block.columns === "number" ? block.columns : void 0
1062
- }, block._key, preserveIdentity)
1063
- };
1064
- if (isCodeBlock(block)) return convertCodeBlock(block, preserveIdentity);
1065
- if (block._type === "htmlBlock") return {
1066
- type: "htmlBlock",
1067
- attrs: identityAttrs({ html: block.html || "" }, block._key, preserveIdentity)
1068
- };
1069
- if (block._type === "break") return {
1070
- type: "horizontalRule",
1071
- attrs: identityAttrs(void 0, block._key, preserveIdentity)
1072
- };
1073
- if (block._type === "gallery") {
1074
- if (preserveIdentity) return {
1075
- type: PORTABLE_TEXT_BLOCK_NODE,
1076
- attrs: { [PORTABLE_TEXT_BLOCK_ATTR]: block }
1077
- };
1078
- return {
1079
- type: "paragraph",
1080
- content: [{
1081
- type: "text",
1082
- text: `[Unknown block type: ${block._type}]`,
1083
- marks: [{ type: "code" }]
1084
- }]
1085
- };
1086
- }
1087
- if (preserveIdentity) return {
1088
- type: PORTABLE_TEXT_BLOCK_NODE,
1089
- attrs: { [PORTABLE_TEXT_BLOCK_ATTR]: block }
1090
- };
1091
- return {
1092
- type: "paragraph",
1093
- content: [{
1094
- type: "text",
1095
- text: `[Unknown block type: ${block._type}]`,
1096
- marks: [{ type: "code" }]
1097
- }]
1098
- };
1099
- }
1100
- /**
1101
- * Convert text block to ProseMirror paragraph or heading
1102
- */
1103
- function convertTextBlock(block, preserveIdentity) {
1104
- const { style = "normal", children, markDefs = [] } = block;
1105
- const content = convertSpans(children, markDefs, preserveIdentity);
1106
- switch (style) {
1107
- case "h1":
1108
- case "h2":
1109
- case "h3":
1110
- case "h4":
1111
- case "h5":
1112
- case "h6": return {
1113
- type: "heading",
1114
- attrs: identityAttrs({
1115
- level: parseInt(style.substring(1), 10),
1116
- ...block.textAlign ? { textAlign: block.textAlign } : {}
1117
- }, block._key, preserveIdentity),
1118
- content: content.length > 0 ? content : void 0
1119
- };
1120
- case "blockquote": return {
1121
- type: "blockquote",
1122
- attrs: identityAttrs(void 0, block._key, preserveIdentity),
1123
- content: [{
1124
- type: "paragraph",
1125
- content: content.length > 0 ? content : void 0
1126
- }]
1127
- };
1128
- default: return {
1129
- type: "paragraph",
1130
- attrs: identityAttrs(block.textAlign ? { textAlign: block.textAlign } : void 0, block._key, preserveIdentity),
1131
- content: content.length > 0 ? content : void 0
1132
- };
1133
- }
1134
- }
1135
- /**
1136
- * Convert list items to ProseMirror list
1137
- */
1138
- function convertList(items, listType, context, preserveIdentity) {
1139
- const rootItems = [];
1140
- let i = 0;
1141
- while (i < items.length) {
1142
- const item = items[i];
1143
- if ((item.level || 1) === 1) {
1144
- const nestedItems = [];
1145
- i++;
1146
- while (i < items.length && (items[i].level || 1) > 1) {
1147
- nestedItems.push(items[i]);
1148
- i++;
1149
- }
1150
- rootItems.push(convertListItem(item, nestedItems, listType, `${context}:${rootItems.length}`, preserveIdentity));
1151
- } else {
1152
- rootItems.push(convertListItem(item, [], listType, `${context}:${rootItems.length}`, preserveIdentity));
1153
- i++;
1154
- }
1155
- }
1156
- const metadata = listType === "number" ? getListMetadata(items[0], `${context}:${items[0]._key}`) : void 0;
1157
- return {
1158
- type: listType === "bullet" ? "bulletList" : "orderedList",
1159
- attrs: metadata ? {
1160
- ...metadata,
1161
- start: metadata.listStart ?? 1
1162
- } : void 0,
1163
- content: rootItems
1164
- };
1165
- }
1166
- /**
1167
- * Convert a single list item to ProseMirror
1168
- */
1169
- function convertListItem(item, nestedItems, parentListType, context, preserveIdentity) {
1170
- const content = [];
1171
- const spans = convertSpans(item.children, item.markDefs || [], preserveIdentity);
1172
- content.push({
1173
- type: "paragraph",
1174
- attrs: identityAttrs(void 0, item._key, preserveIdentity),
1175
- content: spans.length > 0 ? spans : void 0
1176
- });
1177
- if (nestedItems.length > 0) {
1178
- let minLevel = Infinity;
1179
- for (const ni of nestedItems) {
1180
- const level = ni.level || 2;
1181
- if (level < minLevel) minLevel = level;
1182
- }
1183
- let j = 0;
1184
- while (j < nestedItems.length) {
1185
- const anchorType = nestedItems[j].listItem || parentListType;
1186
- const anchorId = anchorType === "number" ? normalizeListId(nestedItems[j].listId) : void 0;
1187
- const nestedGroup = [];
1188
- do {
1189
- nestedGroup.push(nestedItems[j]);
1190
- j++;
1191
- } while (j < nestedItems.length && belongsToNestedGroup(nestedItems[j], minLevel, parentListType, anchorType, anchorId));
1192
- if (nestedGroup.length > 0) {
1193
- const adjustedGroup = nestedGroup.map((ni) => ({
1194
- ...ni,
1195
- level: (ni.level || 2) - 1
1196
- }));
1197
- content.push(convertList(adjustedGroup, anchorType, `${context}:nested:${j - nestedGroup.length}`, preserveIdentity));
1198
- }
1199
- }
1200
- }
1201
- return {
1202
- type: "listItem",
1203
- content
1204
- };
1205
- }
1206
- /**
1207
- * Convert Portable Text spans to ProseMirror text nodes
1208
- */
1209
- function convertSpans(spans, markDefs, preserveIdentity) {
1210
- const nodes = [];
1211
- const markDefsMap = new Map(markDefs.map((md) => [md._key, md]));
1212
- for (const span of spans) {
1213
- if (span._type !== "span") continue;
1214
- const referencedMarkDefs = markDefs.filter((markDef) => span.marks?.includes(markDef._key));
1215
- const parts = span.text.split("\n");
1216
- for (let i = 0; i < parts.length; i++) {
1217
- const text = parts[i];
1218
- if (text.length > 0) {
1219
- const marks = convertMarks(span.marks || [], markDefsMap);
1220
- if (preserveIdentity) marks.push({
1221
- type: PORTABLE_TEXT_SPAN_MARK,
1222
- attrs: {
1223
- key: span._key,
1224
- markDefs: referencedMarkDefs
1225
- }
1226
- });
1227
- const node = {
1228
- type: "text",
1229
- text
1230
- };
1231
- if (marks.length > 0) node.marks = marks;
1232
- nodes.push(node);
1233
- }
1234
- if (i < parts.length - 1) nodes.push(preserveIdentity ? {
1235
- type: "hardBreak",
1236
- marks: [{
1237
- type: PORTABLE_TEXT_SPAN_MARK,
1238
- attrs: {
1239
- key: span._key,
1240
- markDefs: referencedMarkDefs
1241
- }
1242
- }]
1243
- } : { type: "hardBreak" });
1244
- }
1245
- }
1246
- return nodes;
1247
- }
1248
- /**
1249
- * Convert Portable Text marks to ProseMirror marks
1250
- */
1251
- function convertMarks(marks, markDefs) {
1252
- const pmMarks = [];
1253
- for (const mark of marks) switch (mark) {
1254
- case "strong":
1255
- pmMarks.push({ type: "bold" });
1256
- break;
1257
- case "em":
1258
- pmMarks.push({ type: "italic" });
1259
- break;
1260
- case "underline":
1261
- pmMarks.push({ type: "underline" });
1262
- break;
1263
- case "strike-through":
1264
- pmMarks.push({ type: "strike" });
1265
- break;
1266
- case "subscript":
1267
- pmMarks.push({ type: "subscript" });
1268
- break;
1269
- case "superscript":
1270
- pmMarks.push({ type: "superscript" });
1271
- break;
1272
- case "code":
1273
- pmMarks.push({ type: "code" });
1274
- break;
1275
- default: {
1276
- const markDef = markDefs.get(mark);
1277
- if (markDef?._type === "link") pmMarks.push({
1278
- type: "link",
1279
- attrs: {
1280
- href: markDef.href,
1281
- target: markDef.blank ? "_blank" : null
1282
- }
1283
- });
1284
- else throw new UnsupportedPortableTextMarksError([typeof markDef?._type === "string" ? markDef._type : mark]);
1285
- break;
1286
- }
1287
- }
1288
- return pmMarks;
1289
- }
1290
- function imageAlignment(value) {
1291
- return value === "left" || value === "center" || value === "right" || value === "wide" || value === "full" ? value : void 0;
1292
- }
1293
- /**
1294
- * Convert image block to ProseMirror
1295
- */
1296
- function convertImage(block, preserveIdentity) {
1297
- return {
1298
- type: "image",
1299
- attrs: identityAttrs({
1300
- src: block.asset.url || block.asset._ref,
1301
- alt: block.alt || "",
1302
- title: block.caption || "",
1303
- mediaId: block.asset._ref,
1304
- provider: block.asset.provider,
1305
- width: block.width,
1306
- height: block.height,
1307
- displayWidth: block.displayWidth,
1308
- displayHeight: block.displayHeight,
1309
- alignment: imageAlignment(block.alignment)
1310
- }, block._key, preserveIdentity)
1311
- };
1312
- }
1313
- /**
1314
- * Convert a malformed image block (missing `asset` wrapper) to ProseMirror.
1315
- * Handles blocks like `{ _type: "image", url: "...", alt: "..." }` that may
1316
- * originate from migrations or third-party imports.
1317
- */
1318
- function convertMalformedImage(block, preserveIdentity) {
1319
- return {
1320
- type: "image",
1321
- attrs: identityAttrs({
1322
- src: "url" in block && typeof block.url === "string" ? block.url : "",
1323
- alt: "alt" in block && typeof block.alt === "string" ? block.alt : "",
1324
- title: "caption" in block && typeof block.caption === "string" ? block.caption : "",
1325
- mediaId: void 0,
1326
- provider: void 0,
1327
- width: "width" in block && typeof block.width === "number" ? block.width : void 0,
1328
- height: "height" in block && typeof block.height === "number" ? block.height : void 0,
1329
- displayWidth: "displayWidth" in block && typeof block.displayWidth === "number" ? block.displayWidth : void 0,
1330
- displayHeight: "displayHeight" in block && typeof block.displayHeight === "number" ? block.displayHeight : void 0,
1331
- alignment: imageAlignment("alignment" in block ? block.alignment : void 0)
1332
- }, block._key, preserveIdentity)
1333
- };
1334
- }
1335
- /**
1336
- * Convert code block to ProseMirror
1337
- */
1338
- function convertCodeBlock(block, preserveIdentity) {
1339
- return {
1340
- type: "codeBlock",
1341
- attrs: identityAttrs({ language: block.language || null }, block._key, preserveIdentity),
1342
- content: block.code ? [{
1343
- type: "text",
1344
- text: block.code
1345
- }] : void 0
1346
- };
1347
- }
1348
-
1349
- //#endregion
1350
- //#region src/plugins/define-plugin.ts
1351
- /**
1352
- * definePlugin() Helper
1353
- *
1354
- * Native plugin authoring entry. Returns a fully-resolved
1355
- * `ResolvedPlugin` ready for the host integration to mount.
1356
- *
1357
- * Sandboxed plugins do NOT use this function. They default-export
1358
- * a bare `{ hooks?, routes? }` object with a `satisfies SandboxedPlugin`
1359
- * annotation from `emdash/plugin`. See the `emdash` changeset for the
1360
- * authoring shape.
1361
- */
1362
- const MCP_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
1363
- /**
1364
- * Define a native EmDash plugin.
1365
- *
1366
- * Native plugins ship as regular npm modules, get installed via
1367
- * `pnpm add` + an `astro.config.mjs` edit, and run in the host
1368
- * process. They have full access to the runtime — capabilities are
1369
- * still enforced by `PluginContextFactory`, but there is no isolation
1370
- * boundary.
1371
- *
1372
- * @example
1373
- * ```typescript
1374
- * import { definePlugin } from "emdash";
1375
- *
1376
- * export default definePlugin({
1377
- * id: "my-plugin",
1378
- * version: "1.0.0",
1379
- * capabilities: ["content:read"],
1380
- * hooks: {
1381
- * "content:beforeSave": async (event, ctx) => {
1382
- * ctx.log.info("Saving content", { collection: event.collection });
1383
- * return event.content;
1384
- * }
1385
- * },
1386
- * routes: {
1387
- * "sync": {
1388
- * handler: async (ctx) => {
1389
- * return { status: "ok" };
1390
- * }
1391
- * }
1392
- * }
1393
- * });
1394
- * ```
1395
- *
1396
- * Sandboxed-format plugins do not use `definePlugin`. They
1397
- * default-export a bare `{ hooks?, routes? }` object with a
1398
- * `satisfies SandboxedPlugin` annotation from `emdash/plugin`. Calling
1399
- * `definePlugin` with an object that has no `id` throws at runtime
1400
- * (the type system already rejects it at compile time — this check is
1401
- * for callers that bypass typechecking).
1402
- */
1403
- function definePlugin(definition) {
1404
- 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\`.`);
1405
- return defineNativePlugin(definition);
1406
- }
1407
- /**
1408
- * Internal: define a native-format plugin with full validation and normalization.
1409
- */
1410
- function defineNativePlugin(definition) {
1411
- const SIMPLE_ID = /^[a-z0-9-]+$/;
1412
- const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
1413
- const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
1414
- const { id, version, capabilities = [], allowedHosts = [], hooks = {}, routes = {}, mcp = { tools: {} }, admin = {} } = definition;
1415
- const storage = definition.storage ?? {};
1416
- 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").`);
1417
- if (!SEMVER_PATTERN.test(version)) throw new Error(`Invalid plugin version "${version}". Must be semver format (e.g., "1.0.0").`);
1418
- for (const [name, tool] of Object.entries(mcp.tools)) {
1419
- if (!MCP_TOOL_NAME_PATTERN.test(name)) throw new Error(`Invalid MCP tool name "${name}" in plugin "${id}".`);
1420
- const route = routes[tool.route];
1421
- if (!route) throw new Error(`MCP tool "${name}" references unknown route "${tool.route}".`);
1422
- if (route.public) throw new Error(`MCP tool "${name}" cannot reference a public route.`);
1423
- if (!route.permission) throw new Error(`MCP route "${tool.route}" must declare a permission.`);
1424
- }
1425
- const validCapabilities = new Set([
1426
- "network:request",
1427
- "network:request:unrestricted",
1428
- "content:read",
1429
- "content:write",
1430
- "taxonomies:read",
1431
- "media:read",
1432
- "media:write",
1433
- "users:read",
1434
- "email:send",
1435
- "hooks.email-transport:register",
1436
- "hooks.email-events:register",
1437
- "hooks.page-fragments:register",
1438
- "network:fetch",
1439
- "network:fetch:any",
1440
- "read:content",
1441
- "write:content",
1442
- "read:media",
1443
- "write:media",
1444
- "read:users",
1445
- "email:provide",
1446
- "email:intercept",
1447
- "page:inject"
1448
- ]);
1449
- for (const cap of capabilities) if (!validCapabilities.has(cap)) throw new Error(`Invalid capability "${cap}" in plugin "${id}".`);
1450
- const canonical = normalizeCapabilities(capabilities);
1451
- const normalizedCapabilities = [...canonical];
1452
- if (canonical.includes("content:write") && !canonical.includes("content:read")) normalizedCapabilities.push("content:read");
1453
- if (canonical.includes("media:write") && !canonical.includes("media:read")) normalizedCapabilities.push("media:read");
1454
- if (canonical.includes("network:request:unrestricted") && !canonical.includes("network:request")) normalizedCapabilities.push("network:request");
1455
- return {
1456
- id,
1457
- version,
1458
- capabilities: normalizedCapabilities,
1459
- allowedHosts,
1460
- storage,
1461
- hooks: resolveHooks(hooks, id),
1462
- routes,
1463
- mcp,
1464
- admin
1465
- };
1466
- }
1467
- /**
1468
- * Resolve hooks to normalized format with defaults.
1469
- *
1470
- * PluginHooks and ResolvedPluginHooks share the same keys — each input value is
1471
- * `HookConfig<H> | H` and the output is `ResolvedHook<H>`. TS can't narrow
1472
- * the handler type through a dynamic key, so we assert at the loop boundary.
1473
- */
1474
- function resolveHooks(hooks, pluginId) {
1475
- const resolved = {};
1476
- for (const key of Object.keys(hooks)) {
1477
- const hook = hooks[key];
1478
- if (hook) resolved[key] = resolveHook(hook, pluginId);
1479
- }
1480
- return resolved;
1481
- }
1482
- /**
1483
- * Check if a hook value is a config object (has a `handler` property)
1484
- */
1485
- function isHookConfig(hook) {
1486
- return typeof hook === "object" && hook !== null && "handler" in hook;
1487
- }
1488
- /**
1489
- * Resolve a single hook to normalized format
1490
- */
1491
- function resolveHook(hook, pluginId) {
1492
- if (isHookConfig(hook)) {
1493
- if (hook.exclusive !== void 0 && typeof hook.exclusive !== "boolean") throw new Error(`Invalid "exclusive" value in hook config for plugin "${pluginId}". Must be boolean.`);
1494
- return {
1495
- priority: hook.priority ?? 100,
1496
- timeout: hook.timeout ?? 5e3,
1497
- dependencies: hook.dependencies ?? [],
1498
- errorPolicy: hook.errorPolicy ?? "abort",
1499
- exclusive: hook.exclusive ?? false,
1500
- handler: hook.handler,
1501
- pluginId
1502
- };
1503
- }
1504
- return {
1505
- priority: 100,
1506
- timeout: 5e3,
1507
- dependencies: [],
1508
- errorPolicy: "abort",
1509
- exclusive: false,
1510
- handler: hook,
1511
- pluginId
1512
- };
1513
- }
1514
-
1515
- //#endregion
1516
- //#region src/plugins/hooks.ts
1517
- /**
1518
- * Plugin Hooks System v2
1519
- *
1520
- * Uses the unified PluginContext for all hooks.
1521
- * Manages lifecycle hooks with:
1522
- * - Deterministic ordering via priority + dependencies
1523
- * - Timeout enforcement
1524
- * - Error isolation
1525
- * - Observability
1526
- *
1527
- */
1528
- /**
1529
- * Hook pipeline for executing hooks in order
1530
- */
1531
- var HookPipeline = class HookPipeline {
1532
- hooks = /* @__PURE__ */ new Map();
1533
- pluginMap = /* @__PURE__ */ new Map();
1534
- contextFactory = null;
1535
- /** Stored so setContextFactory can merge incrementally. */
1536
- contextFactoryOptions = {};
1537
- /** Hook names where at least one handler declared exclusive: true */
1538
- exclusiveHookNames = /* @__PURE__ */ new Set();
1539
- /**
1540
- * Selected provider plugin ID for each exclusive hook.
1541
- * Set by the PluginManager after resolution.
1542
- */
1543
- exclusiveSelections = /* @__PURE__ */ new Map();
1544
- constructor(plugins, factoryOptions) {
1545
- if (factoryOptions) {
1546
- this.contextFactory = new PluginContextFactory(factoryOptions);
1547
- this.contextFactoryOptions = { ...factoryOptions };
1548
- }
1549
- for (const plugin of plugins) this.pluginMap.set(plugin.id, plugin);
1550
- this.registerPlugins(plugins);
1551
- }
1552
- /**
1553
- * Set or update the context factory options.
1554
- *
1555
- * When called on a pipeline that already has a factory, the new options
1556
- * are merged on top of the existing ones so that callers don't need to
1557
- * repeat every field (e.g. adding `cronReschedule` without losing
1558
- * `storage` / `getUploadUrl`).
1559
- */
1560
- setContextFactory(options) {
1561
- const merged = {
1562
- ...this.contextFactoryOptions,
1563
- ...options
1564
- };
1565
- this.contextFactory = new PluginContextFactory(merged);
1566
- this.contextFactoryOptions = merged;
1567
- }
1568
- /**
1569
- * Get context for a plugin
1570
- */
1571
- getContext(pluginId) {
1572
- const plugin = this.pluginMap.get(pluginId);
1573
- if (!plugin) throw new Error(`Plugin "${pluginId}" not found`);
1574
- if (!this.contextFactory) throw new Error("Context factory not initialized - call setContextFactory first");
1575
- return this.contextFactory.createContext(plugin);
1576
- }
1577
- /**
1578
- * Get typed hooks for a specific hook name.
1579
- * The internal map stores ResolvedHook<unknown>, but we know each name
1580
- * maps to a specific handler type via HookHandlerMap.
1581
- *
1582
- * Exclusive hooks that have a selected provider are filtered out — they
1583
- * should only run via invokeExclusiveHook(), not in the regular pipeline.
1584
- */
1585
- getTypedHooks(name) {
1586
- const all = this.hooks.get(name) ?? [];
1587
- if (this.exclusiveSelections.has(name)) return all.filter((h) => !h.exclusive);
1588
- return all;
1589
- }
1590
- /**
1591
- * Register all hooks from plugins.
1592
- *
1593
- * Registers each hook name individually to preserve type safety. The
1594
- * internal map stores ResolvedHook<unknown> since it's keyed by string,
1595
- * but getTypedHooks() restores the correct handler type on retrieval.
1596
- */
1597
- registerPlugins(plugins) {
1598
- for (const plugin of plugins) {
1599
- this.registerPluginHook(plugin, "plugin:install");
1600
- this.registerPluginHook(plugin, "plugin:activate");
1601
- this.registerPluginHook(plugin, "plugin:deactivate");
1602
- this.registerPluginHook(plugin, "plugin:uninstall");
1603
- this.registerPluginHook(plugin, "content:beforeSave");
1604
- this.registerPluginHook(plugin, "content:afterSave");
1605
- this.registerPluginHook(plugin, "content:beforeDelete");
1606
- this.registerPluginHook(plugin, "content:afterDelete");
1607
- this.registerPluginHook(plugin, "content:afterPublish");
1608
- this.registerPluginHook(plugin, "content:afterUnpublish");
1609
- this.registerPluginHook(plugin, "content:afterRestore");
1610
- this.registerPluginHook(plugin, "content:afterSchedule");
1611
- this.registerPluginHook(plugin, "content:afterUnschedule");
1612
- this.registerPluginHook(plugin, "media:beforeUpload");
1613
- this.registerPluginHook(plugin, "media:afterUpload");
1614
- this.registerPluginHook(plugin, "cron");
1615
- this.registerPluginHook(plugin, "email:beforeSend");
1616
- this.registerPluginHook(plugin, "email:deliver");
1617
- this.registerPluginHook(plugin, "email:afterSend");
1618
- this.registerPluginHook(plugin, "comment:beforeCreate");
1619
- this.registerPluginHook(plugin, "comment:moderate");
1620
- this.registerPluginHook(plugin, "comment:afterCreate");
1621
- this.registerPluginHook(plugin, "comment:afterModerate");
1622
- this.registerPluginHook(plugin, "page:metadata");
1623
- this.registerPluginHook(plugin, "page:fragments");
1624
- }
1625
- for (const [hookName, hooks] of this.hooks) this.hooks.set(hookName, this.sortHooks(hooks));
1626
- }
1627
- /**
1628
- * Maps hook names to the capability required to register them.
1629
- *
1630
- * Hooks not listed here have no capability requirement (e.g. lifecycle
1631
- * hooks, cron). Any plugin declaring a listed hook without the required
1632
- * capability will have that hook silently skipped at registration time.
1633
- */
1634
- static HOOK_REQUIRED_CAPABILITY = new Map([
1635
- ["email:beforeSend", "hooks.email-events:register"],
1636
- ["email:afterSend", "hooks.email-events:register"],
1637
- ["email:deliver", "hooks.email-transport:register"],
1638
- ["content:beforeSave", "content:write"],
1639
- ["content:afterSave", "content:read"],
1640
- ["content:beforeDelete", "content:read"],
1641
- ["content:afterDelete", "content:read"],
1642
- ["content:afterPublish", "content:read"],
1643
- ["content:afterUnpublish", "content:read"],
1644
- ["content:afterRestore", "content:read"],
1645
- ["content:afterSchedule", "content:read"],
1646
- ["content:afterUnschedule", "content:read"],
1647
- ["media:beforeUpload", "media:write"],
1648
- ["media:afterUpload", "media:read"],
1649
- ["comment:beforeCreate", "users:read"],
1650
- ["comment:moderate", "users:read"],
1651
- ["comment:afterCreate", "users:read"],
1652
- ["comment:afterModerate", "users:read"],
1653
- ["page:fragments", "hooks.page-fragments:register"]
1654
- ]);
1655
- /**
1656
- * Register a single plugin's hook by name
1657
- */
1658
- registerPluginHook(plugin, name) {
1659
- const hook = plugin.hooks[name];
1660
- if (!hook) return;
1661
- const requiredCapability = HookPipeline.HOOK_REQUIRED_CAPABILITY.get(name);
1662
- if (requiredCapability && !plugin.capabilities.includes(requiredCapability)) {
1663
- console.warn(`[hooks] Plugin "${plugin.id}" declares ${name} hook without ${requiredCapability} capability — skipping`);
1664
- return;
1665
- }
1666
- if (hook.exclusive) this.exclusiveHookNames.add(name);
1667
- this.registerHook(name, hook);
1668
- }
1669
- /**
1670
- * Register a single hook
1671
- */
1672
- registerHook(name, hook) {
1673
- const existing = this.hooks.get(name) || [];
1674
- existing.push(hook);
1675
- this.hooks.set(name, existing);
1676
- }
1677
- /**
1678
- * Sort hooks by priority and dependencies
1679
- */
1680
- sortHooks(hooks) {
1681
- const sorted = [];
1682
- const remaining = [...hooks];
1683
- while (remaining.length > 0) {
1684
- const ready = remaining.filter((hook) => hook.dependencies.every((dep) => sorted.some((s) => s.pluginId === dep)));
1685
- if (ready.length === 0) {
1686
- const pluginIds = remaining.map((h) => h.pluginId).join(", ");
1687
- console.warn(`[hooks] Hook dependency cycle or missing dependency detected among plugins: ${pluginIds}. Falling back to priority order.`);
1688
- remaining.sort((a, b) => a.priority - b.priority);
1689
- sorted.push(...remaining);
1690
- break;
1691
- }
1692
- ready.sort((a, b) => a.priority - b.priority);
1693
- const next = ready[0];
1694
- sorted.push(next);
1695
- remaining.splice(remaining.indexOf(next), 1);
1696
- }
1697
- return sorted;
1698
- }
1699
- /**
1700
- * Execute a hook with timeout
1701
- */
1702
- async executeWithTimeout(fn, timeout) {
1703
- let timer;
1704
- const timeoutPromise = new Promise((_, reject) => timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Hook timeout after ${timeout}ms`)), timeout));
1705
- try {
1706
- return await Promise.race([fn(), timeoutPromise]);
1707
- } finally {
1708
- clearTimeout(timer);
1709
- }
1710
- }
1711
- /**
1712
- * Run plugin:install hooks
1713
- */
1714
- async runPluginInstall(pluginId) {
1715
- return this.runLifecycleHook("plugin:install", pluginId);
1716
- }
1717
- /**
1718
- * Run plugin:activate hooks
1719
- */
1720
- async runPluginActivate(pluginId) {
1721
- return this.runLifecycleHook("plugin:activate", pluginId);
1722
- }
1723
- /**
1724
- * Run plugin:deactivate hooks
1725
- */
1726
- async runPluginDeactivate(pluginId) {
1727
- return this.runLifecycleHook("plugin:deactivate", pluginId);
1728
- }
1729
- /**
1730
- * Run plugin:uninstall hooks
1731
- */
1732
- async runPluginUninstall(pluginId, deleteData) {
1733
- const hooks = this.getTypedHooks("plugin:uninstall");
1734
- const results = [];
1735
- const hook = hooks.find((h) => h.pluginId === pluginId);
1736
- if (!hook) return results;
1737
- const { handler } = hook;
1738
- const event = { deleteData };
1739
- const ctx = this.getContext(pluginId);
1740
- const start = Date.now();
1741
- try {
1742
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1743
- results.push({
1744
- success: true,
1745
- pluginId: hook.pluginId,
1746
- duration: Date.now() - start
1747
- });
1748
- } catch (error) {
1749
- results.push({
1750
- success: false,
1751
- error: error instanceof Error ? error : new Error(String(error)),
1752
- pluginId: hook.pluginId,
1753
- duration: Date.now() - start
1754
- });
1755
- }
1756
- return results;
1757
- }
1758
- async runLifecycleHook(hookName, pluginId) {
1759
- const hooks = this.getTypedHooks(hookName);
1760
- const results = [];
1761
- const hook = hooks.find((h) => h.pluginId === pluginId);
1762
- if (!hook) return results;
1763
- const { handler } = hook;
1764
- const event = {};
1765
- const ctx = this.getContext(pluginId);
1766
- const start = Date.now();
1767
- try {
1768
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1769
- results.push({
1770
- success: true,
1771
- pluginId: hook.pluginId,
1772
- duration: Date.now() - start
1773
- });
1774
- } catch (error) {
1775
- results.push({
1776
- success: false,
1777
- error: error instanceof Error ? error : new Error(String(error)),
1778
- pluginId: hook.pluginId,
1779
- duration: Date.now() - start
1780
- });
1781
- }
1782
- return results;
1783
- }
1784
- /**
1785
- * Run content:beforeSave hooks
1786
- * Returns modified content from the pipeline. `id` is the existing item's
1787
- * ID when updating.
1788
- */
1789
- async runContentBeforeSave(content, collection, isNew, id) {
1790
- const hooks = this.getTypedHooks("content:beforeSave");
1791
- const results = [];
1792
- let currentContent = content;
1793
- for (const hook of hooks) {
1794
- const { handler } = hook;
1795
- const event = {
1796
- content: currentContent,
1797
- collection,
1798
- isNew
1799
- };
1800
- if (id !== void 0) event.id = id;
1801
- const ctx = this.getContext(hook.pluginId);
1802
- const start = Date.now();
1803
- try {
1804
- const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1805
- if (result !== void 0) currentContent = result;
1806
- results.push({
1807
- success: true,
1808
- value: currentContent,
1809
- pluginId: hook.pluginId,
1810
- duration: Date.now() - start
1811
- });
1812
- } catch (error) {
1813
- results.push({
1814
- success: false,
1815
- error: error instanceof Error ? error : new Error(String(error)),
1816
- pluginId: hook.pluginId,
1817
- duration: Date.now() - start
1818
- });
1819
- if (hook.errorPolicy === "abort") throw error;
1820
- }
1821
- }
1822
- return {
1823
- content: currentContent,
1824
- results
1825
- };
1826
- }
1827
- /**
1828
- * Run content:afterSave hooks
1829
- */
1830
- async runContentAfterSave(content, collection, isNew) {
1831
- const hooks = this.getTypedHooks("content:afterSave");
1832
- const results = [];
1833
- for (const hook of hooks) {
1834
- const { handler } = hook;
1835
- const event = {
1836
- content,
1837
- collection,
1838
- isNew
1839
- };
1840
- const ctx = this.getContext(hook.pluginId);
1841
- const start = Date.now();
1842
- try {
1843
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1844
- results.push({
1845
- success: true,
1846
- pluginId: hook.pluginId,
1847
- duration: Date.now() - start
1848
- });
1849
- } catch (error) {
1850
- results.push({
1851
- success: false,
1852
- error: error instanceof Error ? error : new Error(String(error)),
1853
- pluginId: hook.pluginId,
1854
- duration: Date.now() - start
1855
- });
1856
- if (hook.errorPolicy === "abort") throw error;
1857
- }
1858
- }
1859
- return results;
1860
- }
1861
- /**
1862
- * Run content:beforeDelete hooks
1863
- * Returns whether deletion is allowed
1864
- */
1865
- async runContentBeforeDelete(id, collection) {
1866
- const hooks = this.getTypedHooks("content:beforeDelete");
1867
- const results = [];
1868
- let allowed = true;
1869
- for (const hook of hooks) {
1870
- const { handler } = hook;
1871
- const event = {
1872
- id,
1873
- collection,
1874
- permanent: false
1875
- };
1876
- const ctx = this.getContext(hook.pluginId);
1877
- const start = Date.now();
1878
- try {
1879
- const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1880
- if (result === false) allowed = false;
1881
- results.push({
1882
- success: true,
1883
- value: result !== false,
1884
- pluginId: hook.pluginId,
1885
- duration: Date.now() - start
1886
- });
1887
- } catch (error) {
1888
- results.push({
1889
- success: false,
1890
- error: error instanceof Error ? error : new Error(String(error)),
1891
- pluginId: hook.pluginId,
1892
- duration: Date.now() - start
1893
- });
1894
- if (hook.errorPolicy === "abort") throw error;
1895
- }
1896
- }
1897
- return {
1898
- allowed,
1899
- results
1900
- };
1901
- }
1902
- /**
1903
- * Run content:afterDelete hooks
1904
- */
1905
- async runContentAfterDelete(id, collection, permanent) {
1906
- const hooks = this.getTypedHooks("content:afterDelete");
1907
- const results = [];
1908
- for (const hook of hooks) {
1909
- const { handler } = hook;
1910
- const event = {
1911
- id,
1912
- collection,
1913
- permanent
1914
- };
1915
- const ctx = this.getContext(hook.pluginId);
1916
- const start = Date.now();
1917
- try {
1918
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1919
- results.push({
1920
- success: true,
1921
- pluginId: hook.pluginId,
1922
- duration: Date.now() - start
1923
- });
1924
- } catch (error) {
1925
- results.push({
1926
- success: false,
1927
- error: error instanceof Error ? error : new Error(String(error)),
1928
- pluginId: hook.pluginId,
1929
- duration: Date.now() - start
1930
- });
1931
- if (hook.errorPolicy === "abort") throw error;
1932
- }
1933
- }
1934
- return results;
1935
- }
1936
- /**
1937
- * Run content state-change hooks that all share the same event shape.
1938
- */
1939
- async runContentStateChangeHook(name, content, collection) {
1940
- const hooks = this.getTypedHooks(name);
1941
- const results = [];
1942
- for (const hook of hooks) {
1943
- const { handler } = hook;
1944
- const event = {
1945
- content,
1946
- collection
1947
- };
1948
- const ctx = this.getContext(hook.pluginId);
1949
- const start = Date.now();
1950
- try {
1951
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1952
- results.push({
1953
- success: true,
1954
- pluginId: hook.pluginId,
1955
- duration: Date.now() - start
1956
- });
1957
- } catch (error) {
1958
- results.push({
1959
- success: false,
1960
- error: error instanceof Error ? error : new Error(String(error)),
1961
- pluginId: hook.pluginId,
1962
- duration: Date.now() - start
1963
- });
1964
- if (hook.errorPolicy === "abort") throw error;
1965
- }
1966
- }
1967
- return results;
1968
- }
1969
- /**
1970
- * Run content:afterPublish hooks (fire-and-forget).
1971
- */
1972
- async runContentAfterPublish(content, collection) {
1973
- return this.runContentStateChangeHook("content:afterPublish", content, collection);
1974
- }
1975
- /**
1976
- * Run content:afterUnpublish hooks (fire-and-forget).
1977
- */
1978
- async runContentAfterUnpublish(content, collection) {
1979
- return this.runContentStateChangeHook("content:afterUnpublish", content, collection);
1980
- }
1981
- /**
1982
- * Run content:afterRestore hooks (fire-and-forget).
1983
- */
1984
- async runContentAfterRestore(content, collection) {
1985
- return this.runContentStateChangeHook("content:afterRestore", content, collection);
1986
- }
1987
- /**
1988
- * Run content:afterSchedule hooks (fire-and-forget).
1989
- */
1990
- async runContentAfterSchedule(content, collection) {
1991
- return this.runContentStateChangeHook("content:afterSchedule", content, collection);
1992
- }
1993
- /**
1994
- * Run content:afterUnschedule hooks (fire-and-forget).
1995
- */
1996
- async runContentAfterUnschedule(content, collection) {
1997
- return this.runContentStateChangeHook("content:afterUnschedule", content, collection);
1998
- }
1999
- /**
2000
- * Run media:beforeUpload hooks
2001
- */
2002
- async runMediaBeforeUpload(file) {
2003
- const hooks = this.getTypedHooks("media:beforeUpload");
2004
- const results = [];
2005
- let currentFile = file;
2006
- for (const hook of hooks) {
2007
- const { handler } = hook;
2008
- const event = { file: currentFile };
2009
- const ctx = this.getContext(hook.pluginId);
2010
- const start = Date.now();
2011
- try {
2012
- const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2013
- if (result !== void 0) currentFile = result;
2014
- results.push({
2015
- success: true,
2016
- value: currentFile,
2017
- pluginId: hook.pluginId,
2018
- duration: Date.now() - start
2019
- });
2020
- } catch (error) {
2021
- results.push({
2022
- success: false,
2023
- error: error instanceof Error ? error : new Error(String(error)),
2024
- pluginId: hook.pluginId,
2025
- duration: Date.now() - start
2026
- });
2027
- if (hook.errorPolicy === "abort") throw error;
2028
- }
2029
- }
2030
- return {
2031
- file: currentFile,
2032
- results
2033
- };
2034
- }
2035
- /**
2036
- * Run media:afterUpload hooks
2037
- */
2038
- async runMediaAfterUpload(media) {
2039
- const hooks = this.getTypedHooks("media:afterUpload");
2040
- const results = [];
2041
- for (const hook of hooks) {
2042
- const { handler } = hook;
2043
- const event = { media };
2044
- const ctx = this.getContext(hook.pluginId);
2045
- const start = Date.now();
2046
- try {
2047
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2048
- results.push({
2049
- success: true,
2050
- pluginId: hook.pluginId,
2051
- duration: Date.now() - start
2052
- });
2053
- } catch (error) {
2054
- results.push({
2055
- success: false,
2056
- error: error instanceof Error ? error : new Error(String(error)),
2057
- pluginId: hook.pluginId,
2058
- duration: Date.now() - start
2059
- });
2060
- if (hook.errorPolicy === "abort") throw error;
2061
- }
2062
- }
2063
- return results;
2064
- }
2065
- /**
2066
- * Invoke the cron hook for a specific plugin.
2067
- *
2068
- * Unlike other hooks which broadcast to all plugins, the cron hook is
2069
- * dispatched only to the target plugin — the one that owns the task.
2070
- */
2071
- async invokeCronHook(pluginId, event) {
2072
- const hook = this.getTypedHooks("cron").find((h) => h.pluginId === pluginId);
2073
- if (!hook) return {
2074
- success: false,
2075
- error: /* @__PURE__ */ new Error(`Plugin "${pluginId}" has no cron hook registered`),
2076
- pluginId,
2077
- duration: 0
2078
- };
2079
- const { handler } = hook;
2080
- const ctx = this.getContext(pluginId);
2081
- const start = Date.now();
2082
- try {
2083
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2084
- return {
2085
- success: true,
2086
- pluginId,
2087
- duration: Date.now() - start
2088
- };
2089
- } catch (error) {
2090
- return {
2091
- success: false,
2092
- error: error instanceof Error ? error : new Error(String(error)),
2093
- pluginId,
2094
- duration: Date.now() - start
2095
- };
2096
- }
2097
- }
2098
- /**
2099
- * Run email:beforeSend hooks (middleware pipeline).
2100
- *
2101
- * Each handler receives the message and returns a modified message or
2102
- * `false` to cancel delivery. The pipeline chains message transformations —
2103
- * each handler receives the output of the previous one.
2104
- */
2105
- async runEmailBeforeSend(message, source) {
2106
- const hooks = this.getTypedHooks("email:beforeSend");
2107
- const results = [];
2108
- let currentMessage = message;
2109
- for (const hook of hooks) {
2110
- const { handler } = hook;
2111
- const event = {
2112
- message: { ...currentMessage },
2113
- source
2114
- };
2115
- const ctx = this.getContext(hook.pluginId);
2116
- const start = Date.now();
2117
- try {
2118
- const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2119
- if (result === false) {
2120
- results.push({
2121
- success: true,
2122
- value: false,
2123
- pluginId: hook.pluginId,
2124
- duration: Date.now() - start
2125
- });
2126
- return {
2127
- message: false,
2128
- results
2129
- };
2130
- }
2131
- if (result && typeof result === "object") currentMessage = result;
2132
- results.push({
2133
- success: true,
2134
- value: currentMessage,
2135
- pluginId: hook.pluginId,
2136
- duration: Date.now() - start
2137
- });
2138
- } catch (error) {
2139
- results.push({
2140
- success: false,
2141
- error: error instanceof Error ? error : new Error(String(error)),
2142
- pluginId: hook.pluginId,
2143
- duration: Date.now() - start
2144
- });
2145
- if (hook.errorPolicy === "abort") throw error;
2146
- }
2147
- }
2148
- return {
2149
- message: currentMessage,
2150
- results
2151
- };
2152
- }
2153
- /**
2154
- * Run email:afterSend hooks (fire-and-forget).
2155
- *
2156
- * Errors are logged but don't propagate — they don't affect the caller.
2157
- */
2158
- async runEmailAfterSend(message, source) {
2159
- const hooks = this.getTypedHooks("email:afterSend");
2160
- const results = [];
2161
- for (const hook of hooks) {
2162
- const { handler } = hook;
2163
- const event = {
2164
- message,
2165
- source
2166
- };
2167
- const ctx = this.getContext(hook.pluginId);
2168
- const start = Date.now();
2169
- try {
2170
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2171
- results.push({
2172
- success: true,
2173
- pluginId: hook.pluginId,
2174
- duration: Date.now() - start
2175
- });
2176
- } catch (error) {
2177
- console.error(`[email:afterSend] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2178
- results.push({
2179
- success: false,
2180
- error: error instanceof Error ? error : new Error(String(error)),
2181
- pluginId: hook.pluginId,
2182
- duration: Date.now() - start
2183
- });
2184
- }
2185
- }
2186
- return results;
2187
- }
2188
- /**
2189
- * Run comment:beforeCreate hooks (middleware pipeline).
2190
- *
2191
- * Each handler receives the event and returns a modified event or
2192
- * `false` to reject the comment. The pipeline chains transformations —
2193
- * each handler receives the output of the previous one.
2194
- */
2195
- async runCommentBeforeCreate(event) {
2196
- const hooks = this.getTypedHooks("comment:beforeCreate");
2197
- let currentEvent = event;
2198
- for (const hook of hooks) {
2199
- const { handler } = hook;
2200
- const ctx = this.getContext(hook.pluginId);
2201
- const start = Date.now();
2202
- try {
2203
- const result = await this.executeWithTimeout(() => handler({ ...currentEvent }, ctx), hook.timeout);
2204
- if (result === false) return false;
2205
- if (result && typeof result === "object") currentEvent = result;
2206
- } catch (error) {
2207
- console.error(`[comment:beforeCreate] Plugin "${hook.pluginId}" error (${Date.now() - start}ms):`, error instanceof Error ? error.message : error);
2208
- if (hook.errorPolicy === "abort") throw error;
2209
- }
2210
- }
2211
- return currentEvent;
2212
- }
2213
- /**
2214
- * Run comment:afterCreate hooks (fire-and-forget).
2215
- *
2216
- * Errors are logged but don't propagate — they don't affect the caller.
2217
- */
2218
- async runCommentAfterCreate(event) {
2219
- const hooks = this.getTypedHooks("comment:afterCreate");
2220
- for (const hook of hooks) {
2221
- const { handler } = hook;
2222
- const ctx = this.getContext(hook.pluginId);
2223
- try {
2224
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2225
- } catch (error) {
2226
- console.error(`[comment:afterCreate] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2227
- }
2228
- }
2229
- }
2230
- /**
2231
- * Run comment:afterModerate hooks (fire-and-forget).
2232
- *
2233
- * Errors are logged but don't propagate — they don't affect the caller.
2234
- */
2235
- async runCommentAfterModerate(event) {
2236
- const hooks = this.getTypedHooks("comment:afterModerate");
2237
- for (const hook of hooks) {
2238
- const { handler } = hook;
2239
- const ctx = this.getContext(hook.pluginId);
2240
- try {
2241
- await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
2242
- } catch (error) {
2243
- console.error(`[comment:afterModerate] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2244
- }
2245
- }
2246
- }
2247
- /**
2248
- * Run page:metadata hooks. Each handler returns contributions that are
2249
- * merged by the metadata collector. Errors are logged but don't propagate.
2250
- */
2251
- async runPageMetadata(event) {
2252
- const hooks = this.getTypedHooks("page:metadata");
2253
- const results = [];
2254
- for (const hook of hooks) {
2255
- const { handler } = hook;
2256
- const ctx = this.getContext(hook.pluginId);
2257
- try {
2258
- const result = await this.executeWithTimeout(() => Promise.resolve(handler(event, ctx)), hook.timeout);
2259
- if (result != null) {
2260
- const contributions = Array.isArray(result) ? result : [result];
2261
- results.push({
2262
- pluginId: hook.pluginId,
2263
- contributions
2264
- });
2265
- }
2266
- } catch (error) {
2267
- console.error(`[page:metadata] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2268
- }
2269
- }
2270
- return results;
2271
- }
2272
- /**
2273
- * Run page:fragments hooks. Only trusted plugins should be registered
2274
- * for this hook. Errors are logged but don't propagate.
2275
- */
2276
- async runPageFragments(event) {
2277
- const hooks = this.getTypedHooks("page:fragments");
2278
- const results = [];
2279
- for (const hook of hooks) {
2280
- const { handler } = hook;
2281
- const ctx = this.getContext(hook.pluginId);
2282
- try {
2283
- const result = await this.executeWithTimeout(() => Promise.resolve(handler(event, ctx)), hook.timeout);
2284
- if (result != null) {
2285
- const contributions = Array.isArray(result) ? result : [result];
2286
- results.push({
2287
- pluginId: hook.pluginId,
2288
- contributions
2289
- });
2290
- }
2291
- } catch (error) {
2292
- console.error(`[page:fragments] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
2293
- }
2294
- }
2295
- return results;
2296
- }
2297
- /**
2298
- * Check if any hooks are registered for a given name
2299
- */
2300
- hasHooks(name) {
2301
- const hooks = this.hooks.get(name);
2302
- return hooks !== void 0 && hooks.length > 0;
2303
- }
2304
- /**
2305
- * Get hook count for debugging
2306
- */
2307
- getHookCount(name) {
2308
- return this.hooks.get(name)?.length || 0;
2309
- }
2310
- /**
2311
- * Get all registered hook names
2312
- */
2313
- getRegisteredHooks() {
2314
- return [...this.hooks.keys()];
2315
- }
2316
- /**
2317
- * Returns hook names where at least one handler declared exclusive: true
2318
- */
2319
- getRegisteredExclusiveHooks() {
2320
- return [...this.exclusiveHookNames];
2321
- }
2322
- /**
2323
- * Check if a hook is exclusive
2324
- */
2325
- isExclusiveHook(name) {
2326
- return this.exclusiveHookNames.has(name);
2327
- }
2328
- /**
2329
- * Set the selected provider for an exclusive hook.
2330
- * Called by PluginManager after resolution.
2331
- */
2332
- setExclusiveSelection(hookName, pluginId) {
2333
- this.exclusiveSelections.set(hookName, pluginId);
2334
- }
2335
- /**
2336
- * Clear the selected provider for an exclusive hook.
2337
- */
2338
- clearExclusiveSelection(hookName) {
2339
- this.exclusiveSelections.delete(hookName);
2340
- }
2341
- /**
2342
- * Get the selected provider for an exclusive hook (if any).
2343
- */
2344
- getExclusiveSelection(hookName) {
2345
- return this.exclusiveSelections.get(hookName);
2346
- }
2347
- /**
2348
- * Get all plugins that registered a handler for a given exclusive hook.
2349
- */
2350
- getExclusiveHookProviders(hookName) {
2351
- return (this.hooks.get(hookName) ?? []).filter((h) => h.exclusive).map((h) => ({ pluginId: h.pluginId }));
2352
- }
2353
- /**
2354
- * Get all plugins that registered a non-exclusive handler for a given
2355
- * hook (e.g. `email:beforeSend`, `email:afterSend`), preserving priority
2356
- * order. Partitions with `getExclusiveHookProviders()`, which returns
2357
- * plugins whose registration is marked `exclusive: true`.
2358
- */
2359
- getHookProviders(hookName) {
2360
- return (this.hooks.get(hookName) ?? []).filter((h) => !h.exclusive).map((h) => ({ pluginId: h.pluginId }));
2361
- }
2362
- /**
2363
- * Invoke an exclusive hook — dispatch only to the selected provider.
2364
- * Returns null if no provider is selected or if the selected hook
2365
- * is not found in the pipeline.
2366
- *
2367
- * This is a generic dispatch used by the email pipeline and other
2368
- * exclusive hook consumers. The handler type is unknown — callers
2369
- * must know the expected signature.
2370
- *
2371
- * Errors are isolated: a failing handler returns an error result
2372
- * instead of propagating the exception to the caller.
2373
- */
2374
- async invokeExclusiveHook(hookName, event) {
2375
- const selectedPluginId = this.exclusiveSelections.get(hookName);
2376
- if (!selectedPluginId) return null;
2377
- const hook = (this.hooks.get(hookName) ?? []).find((h) => h.pluginId === selectedPluginId && h.exclusive);
2378
- if (!hook) return null;
2379
- const start = Date.now();
2380
- try {
2381
- const ctx = this.getContext(selectedPluginId);
2382
- const handler = hook.handler;
2383
- return {
2384
- result: await this.executeWithTimeout(() => handler(event, ctx), hook.timeout),
2385
- pluginId: selectedPluginId,
2386
- duration: Date.now() - start
2387
- };
2388
- } catch (error) {
2389
- return {
2390
- result: void 0,
2391
- pluginId: selectedPluginId,
2392
- error: error instanceof Error ? error : new Error(String(error)),
2393
- duration: Date.now() - start
2394
- };
2395
- }
2396
- }
2397
- };
2398
- /**
2399
- * Create a hook pipeline from plugins
2400
- */
2401
- function createHookPipeline(plugins, factoryOptions) {
2402
- return new HookPipeline(plugins, factoryOptions);
2403
- }
2404
- /** Options table key prefix for exclusive hook selections */
2405
- const EXCLUSIVE_HOOK_KEY_PREFIX$1 = "emdash:exclusive_hook:";
2406
- /**
2407
- * Resolve exclusive hook selections.
2408
- *
2409
- * Shared algorithm used by both PluginManager and EmDashRuntime:
2410
- * 1. If a DB selection exists and that plugin is active → keep it.
2411
- * 2. If DB selection is stale (plugin inactive/gone) → clear it.
2412
- * 3. If no selection and only one active provider → auto-select it.
2413
- * 4. If preferred hints match an active provider → first match wins.
2414
- * 5. If multiple providers and no hint → leave unselected (admin must choose).
2415
- */
2416
- async function resolveExclusiveHooks(opts) {
2417
- const { pipeline, isActive, getOption, getOptions, setOption, deleteOption, preferredHints } = opts;
2418
- const exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();
2419
- if (exclusiveHookNames.length === 0) return;
2420
- let batchedSelections;
2421
- if (getOptions) try {
2422
- batchedSelections = await getOptions(exclusiveHookNames.map((hookName) => `${EXCLUSIVE_HOOK_KEY_PREFIX$1}${hookName}`));
2423
- } catch {
2424
- return;
2425
- }
2426
- for (const hookName of exclusiveHookNames) {
2427
- const providers = pipeline.getExclusiveHookProviders(hookName);
2428
- const activeProviderIds = new Set(providers.map((p) => p.pluginId).filter((id) => isActive(id)));
2429
- const key = `${EXCLUSIVE_HOOK_KEY_PREFIX$1}${hookName}`;
2430
- let currentSelection = null;
2431
- if (batchedSelections) currentSelection = batchedSelections.get(key) ?? null;
2432
- else try {
2433
- currentSelection = await getOption(key);
2434
- } catch {
2435
- continue;
2436
- }
2437
- if (currentSelection && activeProviderIds.has(currentSelection)) {
2438
- pipeline.setExclusiveSelection(hookName, currentSelection);
2439
- continue;
2440
- }
2441
- if (currentSelection) try {
2442
- await deleteOption(key);
2443
- } catch {}
2444
- if (activeProviderIds.size === 1) {
2445
- const [onlyProvider] = activeProviderIds;
2446
- try {
2447
- await setOption(key, onlyProvider);
2448
- } catch {}
2449
- pipeline.setExclusiveSelection(hookName, onlyProvider);
2450
- continue;
2451
- }
2452
- if (preferredHints) {
2453
- let found = false;
2454
- for (const [pluginId, hooks] of preferredHints) if (hooks.includes(hookName) && activeProviderIds.has(pluginId)) {
2455
- try {
2456
- await setOption(key, pluginId);
2457
- } catch {}
2458
- pipeline.setExclusiveSelection(hookName, pluginId);
2459
- found = true;
2460
- break;
2461
- }
2462
- if (found) continue;
2463
- }
2464
- pipeline.clearExclusiveSelection(hookName);
2465
- }
2466
- }
2467
-
2468
- //#endregion
2469
- //#region src/plugins/save-rejection.ts
2470
- /**
2471
- * Thrown by a `content:beforeSave` hook to reject a save with a message
2472
- * that is safe to show to the editor. The runtime converts it into a
2473
- * structured `SAVE_REJECTED` API error; any other exception thrown by the
2474
- * hook cancels the save with a generic error that hides the exception
2475
- * message.
2476
- */
2477
- var ContentSaveRejectedError = class extends Error {
2478
- name = "ContentSaveRejectedError";
2479
- };
2480
- /**
2481
- * Matches by name as well as by prototype: bundlers can duplicate this
2482
- * module across SSR chunks, and an `instanceof` against the wrong copy of
2483
- * the class would misreport a rejection as a plugin crash.
2484
- */
2485
- function isContentSaveRejection(error) {
2486
- if (error instanceof ContentSaveRejectedError) return true;
2487
- return error instanceof Error && error.name === "ContentSaveRejectedError";
2488
- }
2489
-
2490
- //#endregion
2491
- //#region src/plugins/email.ts
2492
- /**
2493
- * Email Pipeline
2494
- *
2495
- * Orchestrates the three-stage email pipeline:
2496
- * 1. email:beforeSend hooks (middleware — transform, validate, cancel)
2497
- * 2. email:deliver hook (exclusive — exactly one provider delivers)
2498
- * 3. email:afterSend hooks (logging, analytics, fire-and-forget)
2499
- *
2500
- * Security features:
2501
- * - Recursion guard prevents re-entrant sends (e.g. plugin calling ctx.email.send from a hook)
2502
- * - System emails (source="system") bypass email:beforeSend and email:afterSend hooks entirely
2503
- * to protect auth tokens from exfiltration by plugin hooks
2504
- *
2505
- */
2506
- /** Hook name for the exclusive email delivery hook */
2507
- const EMAIL_DELIVER_HOOK = "email:deliver";
2508
- /** Source value used for auth emails (magic links, invites, password resets) */
2509
- const SYSTEM_SOURCE = "system";
2510
- /**
2511
- * Error thrown when ctx.email.send() is called but no provider is configured.
2512
- */
2513
- var EmailNotConfiguredError = class extends Error {
2514
- constructor() {
2515
- super("No email provider is configured. Install and activate an email provider plugin, then select it in Settings > Email.");
2516
- this.name = "EmailNotConfiguredError";
2517
- }
2518
- };
2519
- /**
2520
- * Error thrown when a recursive email send is detected.
2521
- */
2522
- var EmailRecursionError = class extends Error {
2523
- constructor() {
2524
- super("Recursive email send detected. A plugin hook attempted to send an email from within the email pipeline, which would cause infinite recursion.");
2525
- this.name = "EmailRecursionError";
2526
- }
2527
- };
2528
- /**
2529
- * Recursion guard using AsyncLocalStorage.
2530
- *
2531
- * EmailPipeline is a singleton (worker-lifetime cached via EmDashRuntime).
2532
- * Instance state like `sendDepth` would false-positive under concurrent
2533
- * requests because two unrelated sends would increment the same counter.
2534
- * ALS scopes the guard to the current async execution context, so concurrent
2535
- * requests each get their own independent recursion tracking.
2536
- */
2537
- const emailSendALS = new AsyncLocalStorage();
2538
- /**
2539
- * EmailPipeline orchestrates email delivery through the plugin hook system.
2540
- *
2541
- * The pipeline runs in three stages:
2542
- * 1. email:beforeSend — middleware hooks that can transform or cancel messages
2543
- * 2. email:deliver — exclusive hook dispatching to the selected provider
2544
- * 3. email:afterSend — fire-and-forget hooks for logging/analytics
2545
- */
2546
- var EmailPipeline = class {
2547
- pipeline;
2548
- constructor(pipeline) {
2549
- this.pipeline = pipeline;
2550
- }
2551
- /**
2552
- * Replace the underlying hook pipeline.
2553
- *
2554
- * Called by the runtime when rebuilding the hook pipeline after a
2555
- * plugin is enabled or disabled, so the email pipeline dispatches
2556
- * to the current set of active hooks.
2557
- */
2558
- setPipeline(pipeline) {
2559
- this.pipeline = pipeline;
2560
- }
2561
- /**
2562
- * Send an email through the full pipeline.
2563
- *
2564
- * @param message - The email to send
2565
- * @param source - Where the email originated ("system" for auth, plugin ID for plugins)
2566
- * @throws EmailNotConfiguredError if no provider is selected
2567
- * @throws EmailRecursionError if called re-entrantly from within a hook
2568
- * @throws Error if the provider handler throws
2569
- */
2570
- async send(message, source) {
2571
- const store = emailSendALS.getStore();
2572
- if (store && store.depth > 0) throw new EmailRecursionError();
2573
- const run = () => this.sendInner(message, source);
2574
- if (store) {
2575
- store.depth++;
2576
- try {
2577
- await run();
2578
- } finally {
2579
- store.depth--;
2580
- }
2581
- } else await emailSendALS.run({ depth: 1 }, run);
2582
- }
2583
- /**
2584
- * Inner send implementation, separated from the recursion guard.
2585
- */
2586
- async sendInner(message, source) {
2587
- if (!message || typeof message !== "object") throw new Error("Invalid email message: message must be an object");
2588
- if (!message.to || typeof message.to !== "string") throw new Error("Invalid email message: 'to' is required and must be a string");
2589
- if (!message.subject || typeof message.subject !== "string") throw new Error("Invalid email message: 'subject' is required and must be a string");
2590
- if (!message.text || typeof message.text !== "string") throw new Error("Invalid email message: 'text' is required and must be a string");
2591
- const isSystemEmail = source === SYSTEM_SOURCE;
2592
- let finalMessage;
2593
- if (isSystemEmail) finalMessage = message;
2594
- else {
2595
- const beforeResult = await this.pipeline.runEmailBeforeSend(message, source);
2596
- if (beforeResult.message === false) {
2597
- const cancelledBy = beforeResult.results.find((r) => r.value === false)?.pluginId ?? "unknown";
2598
- console.info(`[email] Email to "${message.to}" cancelled by plugin "${cancelledBy}"`);
2599
- return;
2600
- }
2601
- finalMessage = beforeResult.message;
2602
- }
2603
- const deliverEvent = {
2604
- message: finalMessage,
2605
- source
2606
- };
2607
- const deliverResult = await this.pipeline.invokeExclusiveHook(EMAIL_DELIVER_HOOK, deliverEvent);
2608
- if (!deliverResult) throw new EmailNotConfiguredError();
2609
- if (deliverResult.error) throw deliverResult.error;
2610
- if (!isSystemEmail) this.pipeline.runEmailAfterSend(finalMessage, source).catch((err) => console.error("[email] afterSend pipeline error:", err instanceof Error ? err.message : err));
2611
- }
2612
- /**
2613
- * Check if an email provider is configured and available.
2614
- *
2615
- * Returns true if an email:deliver provider is selected in the exclusive
2616
- * hook system. Plugins and auth code use this to decide whether to show
2617
- * "send invite" vs "copy invite link" UI.
2618
- */
2619
- isAvailable() {
2620
- return this.pipeline.getExclusiveSelection(EMAIL_DELIVER_HOOK) !== void 0;
2621
- }
2622
- };
2623
-
2624
- //#endregion
2625
- //#region src/plugins/routes.ts
2626
- /**
2627
- * Plugin Routes v2
2628
- *
2629
- * Handles plugin API route invocation with:
2630
- * - Input validation via Zod schemas
2631
- * - Route context creation
2632
- * - Error handling
2633
- *
2634
- */
2635
- /**
2636
- * Body-reading methods on `Request`. EmDash parses the request body once before
2637
- * the handler runs and exposes the result as `ctx.input`, leaving the underlying
2638
- * stream consumed. Calling any of these on `ctx.request` would re-read a spent
2639
- * stream and throw an opaque platform error ("Body is unusable: Body has already
2640
- * been read") with no hint about `ctx.input` — so the guard replaces them with an
2641
- * actionable message instead (#1293).
2642
- */
2643
- const CONSUMED_BODY_METHODS = new Set([
2644
- "json",
2645
- "text",
2646
- "arrayBuffer",
2647
- "blob",
2648
- "formData",
2649
- "bytes"
2650
- ]);
2651
- /**
2652
- * Wrap the request handed to a plugin route handler so an accidental
2653
- * `ctx.request.json()` (or `.text()`, `.formData()`, …) fails with a message
2654
- * pointing at `ctx.input` rather than the runtime's cryptic "body already read"
2655
- * error. Every non-body member passes through unchanged; function members are
2656
- * bound to the underlying request so methods like `clone()` don't throw an
2657
- * "Illegal invocation" when called on the proxy.
2658
- */
2659
- function guardConsumedRequestBody(request) {
2660
- return new Proxy(request, { get(target, prop) {
2661
- if (typeof prop === "string" && CONSUMED_BODY_METHODS.has(prop)) return () => {
2662
- throw new Error(`[emdash] ctx.request.${prop}() is not available inside a plugin route handler: EmDash has already parsed the request body and exposes it as ctx.input. Read ctx.input instead of ctx.request.${prop}().`);
2663
- };
2664
- const value = Reflect.get(target, prop, target);
2665
- return typeof value === "function" ? value.bind(target) : value;
2666
- } });
2667
- }
2668
- /**
2669
- * Build RouteMeta from a route's `public`/`cacheControl` flags. Single source
2670
- * of truth for the "cacheControl is only ever exposed on public routes"
2671
- * invariant — used for trusted routes and manifest-declared sandboxed routes.
2672
- */
2673
- function buildRouteMeta(route) {
2674
- const meta = { public: route.public === true };
2675
- if (route.permission !== void 0) meta.permission = route.permission;
2676
- if (meta.public && typeof route.cacheControl === "string" && route.cacheControl.length > 0) meta.cacheControl = route.cacheControl;
2677
- return meta;
2678
- }
2679
- /**
2680
- * HTTP methods that carry a request body. Everything else (GET, HEAD, DELETE)
2681
- * takes its route input from the URL query string.
2682
- */
2683
- const BODY_METHODS = new Set([
2684
- "POST",
2685
- "PUT",
2686
- "PATCH"
2687
- ]);
2688
- /**
2689
- * Parse a plugin route's input from the request, by method.
2690
- *
2691
- * Body methods (POST/PUT/PATCH) parse the JSON body as before. Bodyless
2692
- * methods (GET/HEAD/DELETE) have no body, so `request.json()` resolves to
2693
- * undefined and fails schema validation (#2146) — parse the query string into
2694
- * an object instead. Repeated keys (`?tag=a&tag=b`) become an array so array
2695
- * schemas work; a single key stays a scalar.
2696
- */
2697
- async function parseRouteInput(request) {
2698
- if (BODY_METHODS.has(request.method.toUpperCase())) try {
2699
- return await request.json();
2700
- } catch {
2701
- return;
2702
- }
2703
- const params = new URL(request.url).searchParams;
2704
- const input = {};
2705
- for (const key of new Set(params.keys())) {
2706
- const values = params.getAll(key);
2707
- input[key] = values.length > 1 ? values : values[0];
2708
- }
2709
- return input;
2710
- }
2711
- /**
2712
- * Convert the host's authenticated user into the read-only `UserInfo` shape
2713
- * exposed to plugins as `ctx.user`. Strips sensitive/irrelevant fields and
2714
- * keeps the value structured-clone-safe for sandboxed plugins.
2715
- */
2716
- function toRouteCallerInfo(user) {
2717
- return {
2718
- id: user.id,
2719
- email: user.email,
2720
- name: user.name,
2721
- role: user.role,
2722
- createdAt: typeof user.createdAt === "string" ? user.createdAt : user.createdAt.toISOString()
2723
- };
2724
- }
2725
- /**
2726
- * Route handler for a plugin
2727
- */
2728
- var PluginRouteHandler = class {
2729
- contextFactory;
2730
- plugin;
2731
- trustedProxyHeaders;
2732
- constructor(plugin, factoryOptions) {
2733
- this.plugin = plugin;
2734
- this.contextFactory = new PluginContextFactory(factoryOptions);
2735
- this.trustedProxyHeaders = factoryOptions.trustedProxyHeaders ?? [];
2736
- }
2737
- /**
2738
- * Invoke a route by name
2739
- */
2740
- async invoke(routeName, options) {
2741
- const route = this.plugin.routes[routeName];
2742
- if (!route) return {
2743
- success: false,
2744
- error: {
2745
- code: "ROUTE_NOT_FOUND",
2746
- message: `Route "${routeName}" not found in plugin "${this.plugin.id}"`
2747
- },
2748
- status: 404
2749
- };
2750
- let validatedInput;
2751
- if (route.input) {
2752
- const parseResult = route.input.safeParse(options.body);
2753
- if (!parseResult.success) return {
2754
- success: false,
2755
- error: {
2756
- code: "VALIDATION_ERROR",
2757
- message: "Invalid request body",
2758
- details: z$1.formatError(parseResult.error)
2759
- },
2760
- status: 400
2761
- };
2762
- validatedInput = parseResult.data;
2763
- } else validatedInput = options.body;
2764
- const routeContext = {
2765
- ...this.contextFactory.createContext(this.plugin),
2766
- input: validatedInput,
2767
- request: guardConsumedRequestBody(options.request),
2768
- requestMeta: extractRequestMeta(options.request, this.trustedProxyHeaders),
2769
- user: options.user
2770
- };
2771
- try {
2772
- return {
2773
- success: true,
2774
- data: await route.handler(routeContext),
2775
- status: 200
2776
- };
2777
- } catch (error) {
2778
- if (error instanceof MediaUsageActivationWriteBlockedError) return {
2779
- success: false,
2780
- error: {
2781
- code: error.code,
2782
- message: error.message
2783
- },
2784
- status: error.status
2785
- };
2786
- if (error instanceof PluginRouteError) return {
2787
- success: false,
2788
- error: {
2789
- code: error.code,
2790
- message: error.message,
2791
- details: error.details
2792
- },
2793
- status: error.status
2794
- };
2795
- console.error(`[plugin:${this.plugin.id}] Route handler failed:`, error);
2796
- return {
2797
- success: false,
2798
- error: {
2799
- code: "INTERNAL_ERROR",
2800
- message: "An internal error occurred"
2801
- },
2802
- status: 500
2803
- };
2804
- }
2805
- }
2806
- /**
2807
- * Get all route names
2808
- */
2809
- getRouteNames() {
2810
- return Object.keys(this.plugin.routes);
2811
- }
2812
- /**
2813
- * Check if a route exists
2814
- */
2815
- hasRoute(name) {
2816
- return name in this.plugin.routes;
2817
- }
2818
- /**
2819
- * Get route metadata without invoking the handler.
2820
- * Returns null if the route doesn't exist.
2821
- */
2822
- getRouteMeta(name) {
2823
- const route = this.plugin.routes[name];
2824
- if (!route) return null;
2825
- return buildRouteMeta(route);
2826
- }
2827
- };
2828
- /**
2829
- * Error class for plugin routes
2830
- * Allows plugins to return structured errors with specific HTTP status codes
2831
- */
2832
- var PluginRouteError = class PluginRouteError extends Error {
2833
- constructor(code, message, status = 400, details) {
2834
- super(message);
2835
- this.code = code;
2836
- this.status = status;
2837
- this.details = details;
2838
- this.name = "PluginRouteError";
2839
- }
2840
- /**
2841
- * Create a bad request error (400)
2842
- */
2843
- static badRequest(message, details) {
2844
- return new PluginRouteError("BAD_REQUEST", message, 400, details);
2845
- }
2846
- /**
2847
- * Create an unauthorized error (401)
2848
- */
2849
- static unauthorized(message = "Unauthorized") {
2850
- return new PluginRouteError("UNAUTHORIZED", message, 401);
2851
- }
2852
- /**
2853
- * Create a forbidden error (403)
2854
- */
2855
- static forbidden(message = "Forbidden") {
2856
- return new PluginRouteError("FORBIDDEN", message, 403);
2857
- }
2858
- /**
2859
- * Create a not found error (404)
2860
- */
2861
- static notFound(message = "Not found") {
2862
- return new PluginRouteError("NOT_FOUND", message, 404);
2863
- }
2864
- /**
2865
- * Create a conflict error (409)
2866
- */
2867
- static conflict(message, details) {
2868
- return new PluginRouteError("CONFLICT", message, 409, details);
2869
- }
2870
- /**
2871
- * Create an internal error (500)
2872
- */
2873
- static internal(message = "Internal error") {
2874
- return new PluginRouteError("INTERNAL_ERROR", message, 500);
2875
- }
2876
- };
2877
- /**
2878
- * Registry for all plugin route handlers
2879
- */
2880
- var PluginRouteRegistry = class {
2881
- handlers = /* @__PURE__ */ new Map();
2882
- constructor(factoryOptions) {
2883
- this.factoryOptions = factoryOptions;
2884
- }
2885
- /**
2886
- * Register a plugin's routes
2887
- */
2888
- register(plugin) {
2889
- const handler = new PluginRouteHandler(plugin, this.factoryOptions);
2890
- this.handlers.set(plugin.id, handler);
2891
- }
2892
- /**
2893
- * Unregister a plugin's routes
2894
- */
2895
- unregister(pluginId) {
2896
- this.handlers.delete(pluginId);
2897
- }
2898
- /**
2899
- * Invoke a plugin route
2900
- */
2901
- async invoke(pluginId, routeName, options) {
2902
- const handler = this.handlers.get(pluginId);
2903
- if (!handler) return {
2904
- success: false,
2905
- error: {
2906
- code: "PLUGIN_NOT_FOUND",
2907
- message: `Plugin "${pluginId}" not found`
2908
- },
2909
- status: 404
2910
- };
2911
- return handler.invoke(routeName, options);
2912
- }
2913
- /**
2914
- * Get all registered plugin IDs
2915
- */
2916
- getPluginIds() {
2917
- return [...this.handlers.keys()];
2918
- }
2919
- /**
2920
- * Get routes for a plugin
2921
- */
2922
- getRoutes(pluginId) {
2923
- return this.handlers.get(pluginId)?.getRouteNames() ?? [];
2924
- }
2925
- /**
2926
- * Get route metadata for a specific plugin route.
2927
- * Returns null if the plugin or route doesn't exist.
2928
- */
2929
- getRouteMeta(pluginId, routeName) {
2930
- const handler = this.handlers.get(pluginId);
2931
- if (!handler) return null;
2932
- return handler.getRouteMeta(routeName);
2933
- }
2934
- };
2935
-
2936
- //#endregion
2937
- //#region src/plugins/manager.ts
2938
- /** Options table key prefix for exclusive hook DB reads via PluginManager */
2939
- const EXCLUSIVE_HOOK_KEY_PREFIX = "emdash:exclusive_hook:";
2940
- /**
2941
- * Plugin Manager v2
2942
- *
2943
- * Manages the full lifecycle of plugins and coordinates hooks/routes.
2944
- */
2945
- var PluginManager = class {
2946
- plugins = /* @__PURE__ */ new Map();
2947
- hookPipeline = null;
2948
- routeRegistry = null;
2949
- factoryOptions;
2950
- initialized = false;
2951
- constructor(options) {
2952
- this.options = options;
2953
- this.factoryOptions = {
2954
- db: options.db,
2955
- storage: options.storage,
2956
- getUploadUrl: options.getUploadUrl,
2957
- trustedProxyHeaders: options.trustedProxyHeaders
2958
- };
2959
- }
2960
- /**
2961
- * Set the email pipeline used when creating plugin contexts.
2962
- * Reinitializes routes/hooks if already initialized so ctx.email is available immediately.
2963
- */
2964
- setEmailPipeline(pipeline) {
2965
- this.factoryOptions.emailPipeline = pipeline;
2966
- if (this.initialized) this.reinitialize();
2967
- }
2968
- /**
2969
- * Register a plugin definition
2970
- * This resolves the definition and adds it to the manager, but doesn't install it
2971
- */
2972
- register(definition) {
2973
- const resolved = definePlugin(definition);
2974
- if (this.plugins.has(resolved.id)) throw new Error(`Plugin "${resolved.id}" is already registered`);
2975
- this.plugins.set(resolved.id, {
2976
- plugin: resolved,
2977
- state: "registered"
2978
- });
2979
- this.initialized = false;
2980
- return resolved;
2981
- }
2982
- /**
2983
- * Register multiple plugins
2984
- */
2985
- registerAll(definitions) {
2986
- for (const def of definitions) this.register(def);
2987
- }
2988
- /**
2989
- * Unregister a plugin
2990
- * Plugin must be inactive or just registered
2991
- */
2992
- unregister(pluginId) {
2993
- const entry = this.plugins.get(pluginId);
2994
- if (!entry) return false;
2995
- if (entry.state === "active") throw new Error(`Cannot unregister active plugin "${pluginId}". Deactivate it first.`);
2996
- this.plugins.delete(pluginId);
2997
- this.initialized = false;
2998
- return true;
2999
- }
3000
- /**
3001
- * Install a plugin (run install hooks, set up storage)
3002
- */
3003
- async install(pluginId) {
3004
- const entry = this.plugins.get(pluginId);
3005
- if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
3006
- if (entry.state !== "registered") throw new Error(`Plugin "${pluginId}" is already installed (state: ${entry.state})`);
3007
- this.ensureInitialized();
3008
- const results = await this.hookPipeline.runPluginInstall(pluginId);
3009
- const failed = results.find((r) => !r.success);
3010
- if (failed) throw new Error(`Plugin install failed: ${failed.error?.message ?? "Unknown error"}`);
3011
- entry.state = "installed";
3012
- return results;
3013
- }
3014
- /**
3015
- * Activate a plugin (run activate hooks, enable hooks/routes)
3016
- */
3017
- async activate(pluginId) {
3018
- const entry = this.plugins.get(pluginId);
3019
- if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
3020
- if (entry.state === "active") return [];
3021
- if (entry.state === "registered") await this.install(pluginId);
3022
- this.ensureInitialized();
3023
- const results = await this.hookPipeline.runPluginActivate(pluginId);
3024
- const failed = results.find((r) => !r.success);
3025
- if (failed) throw new Error(`Plugin activation failed: ${failed.error?.message ?? "Unknown error"}`);
3026
- entry.state = "active";
3027
- await setCronTasksEnabled(this.options.db, pluginId, true);
3028
- this.reinitialize();
3029
- await this.resolveExclusiveHooks();
3030
- return results;
3031
- }
3032
- /**
3033
- * Deactivate a plugin (run deactivate hooks, disable hooks/routes)
3034
- */
3035
- async deactivate(pluginId) {
3036
- const entry = this.plugins.get(pluginId);
3037
- if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
3038
- if (entry.state !== "active") return [];
3039
- this.ensureInitialized();
3040
- const results = await this.hookPipeline.runPluginDeactivate(pluginId);
3041
- await setCronTasksEnabled(this.options.db, pluginId, false);
3042
- entry.state = "inactive";
3043
- this.reinitialize();
3044
- await this.resolveExclusiveHooks();
3045
- return results;
3046
- }
3047
- /**
3048
- * Uninstall a plugin (run uninstall hooks, optionally delete data)
3049
- */
3050
- async uninstall(pluginId, deleteData = false) {
3051
- const entry = this.plugins.get(pluginId);
3052
- if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
3053
- if (entry.state === "active") await this.deactivate(pluginId);
3054
- this.ensureInitialized();
3055
- const results = await this.hookPipeline.runPluginUninstall(pluginId, deleteData);
3056
- await this.deleteCronTasks(pluginId);
3057
- this.plugins.delete(pluginId);
3058
- this.initialized = false;
3059
- await this.resolveExclusiveHooks();
3060
- return results;
3061
- }
3062
- /**
3063
- * Run content:beforeSave hooks across all active plugins
3064
- */
3065
- async runContentBeforeSave(content, collection, isNew, id) {
3066
- this.ensureInitialized();
3067
- return this.hookPipeline.runContentBeforeSave(content, collection, isNew, id);
3068
- }
3069
- /**
3070
- * Run content:afterSave hooks across all active plugins
3071
- */
3072
- async runContentAfterSave(content, collection, isNew) {
3073
- this.ensureInitialized();
3074
- return this.hookPipeline.runContentAfterSave(content, collection, isNew);
3075
- }
3076
- /**
3077
- * Run content:beforeDelete hooks across all active plugins
3078
- */
3079
- async runContentBeforeDelete(id, collection) {
3080
- this.ensureInitialized();
3081
- return this.hookPipeline.runContentBeforeDelete(id, collection);
3082
- }
3083
- /**
3084
- * Run content:afterDelete hooks across all active plugins
3085
- */
3086
- async runContentAfterDelete(id, collection, permanent) {
3087
- this.ensureInitialized();
3088
- return this.hookPipeline.runContentAfterDelete(id, collection, permanent);
3089
- }
3090
- /**
3091
- * Run content:afterPublish hooks across all active plugins
3092
- */
3093
- async runContentAfterPublish(content, collection) {
3094
- this.ensureInitialized();
3095
- return this.hookPipeline.runContentAfterPublish(content, collection);
3096
- }
3097
- /**
3098
- * Run content:afterUnpublish hooks across all active plugins
3099
- */
3100
- async runContentAfterUnpublish(content, collection) {
3101
- this.ensureInitialized();
3102
- return this.hookPipeline.runContentAfterUnpublish(content, collection);
3103
- }
3104
- /**
3105
- * Run content:afterRestore hooks across all active plugins
3106
- */
3107
- async runContentAfterRestore(content, collection) {
3108
- this.ensureInitialized();
3109
- return this.hookPipeline.runContentAfterRestore(content, collection);
3110
- }
3111
- /**
3112
- * Run content:afterSchedule hooks across all active plugins
3113
- */
3114
- async runContentAfterSchedule(content, collection) {
3115
- this.ensureInitialized();
3116
- return this.hookPipeline.runContentAfterSchedule(content, collection);
3117
- }
3118
- /**
3119
- * Run content:afterUnschedule hooks across all active plugins
3120
- */
3121
- async runContentAfterUnschedule(content, collection) {
3122
- this.ensureInitialized();
3123
- return this.hookPipeline.runContentAfterUnschedule(content, collection);
3124
- }
3125
- /**
3126
- * Run media:beforeUpload hooks across all active plugins
3127
- */
3128
- async runMediaBeforeUpload(file) {
3129
- this.ensureInitialized();
3130
- return this.hookPipeline.runMediaBeforeUpload(file);
3131
- }
3132
- /**
3133
- * Run media:afterUpload hooks across all active plugins
3134
- */
3135
- async runMediaAfterUpload(media) {
3136
- this.ensureInitialized();
3137
- return this.hookPipeline.runMediaAfterUpload(media);
3138
- }
3139
- /**
3140
- * Invoke the cron hook for a specific plugin (per-plugin dispatch).
3141
- * Used as the InvokeCronHookFn callback for CronExecutor.
3142
- */
3143
- async invokeCronHook(pluginId, event) {
3144
- this.ensureInitialized();
3145
- const result = await this.hookPipeline.invokeCronHook(pluginId, event);
3146
- if (!result.success && result.error) throw result.error;
3147
- }
3148
- /**
3149
- * Invoke a plugin route
3150
- */
3151
- async invokeRoute(pluginId, routeName, options) {
3152
- this.ensureInitialized();
3153
- return this.routeRegistry.invoke(pluginId, routeName, options);
3154
- }
3155
- /**
3156
- * Get all routes for a plugin
3157
- */
3158
- getPluginRoutes(pluginId) {
3159
- this.ensureInitialized();
3160
- return this.routeRegistry.getRoutes(pluginId);
3161
- }
3162
- /**
3163
- * Get a plugin by ID
3164
- */
3165
- getPlugin(pluginId) {
3166
- return this.plugins.get(pluginId)?.plugin;
3167
- }
3168
- /**
3169
- * Get plugin state
3170
- */
3171
- getPluginState(pluginId) {
3172
- return this.plugins.get(pluginId)?.state;
3173
- }
3174
- /**
3175
- * Get all registered plugins
3176
- */
3177
- getAllPlugins() {
3178
- return Array.from(this.plugins.values(), (entry) => ({
3179
- plugin: entry.plugin,
3180
- state: entry.state
3181
- }));
3182
- }
3183
- /**
3184
- * Get all active plugins
3185
- */
3186
- getActivePlugins() {
3187
- return [...this.plugins.values()].filter((entry) => entry.state === "active").map((entry) => entry.plugin);
3188
- }
3189
- /**
3190
- * Check if a plugin exists
3191
- */
3192
- hasPlugin(pluginId) {
3193
- return this.plugins.has(pluginId);
3194
- }
3195
- /**
3196
- * Check if a plugin is active
3197
- */
3198
- isActive(pluginId) {
3199
- return this.plugins.get(pluginId)?.state === "active";
3200
- }
3201
- /**
3202
- * Get all plugins that registered a handler for an exclusive hook.
3203
- */
3204
- getExclusiveHookProviders(hookName) {
3205
- this.ensureInitialized();
3206
- return this.hookPipeline.getExclusiveHookProviders(hookName).map((p) => {
3207
- const plugin = this.plugins.get(p.pluginId);
3208
- return {
3209
- pluginId: p.pluginId,
3210
- pluginName: plugin?.plugin.id ?? p.pluginId
3211
- };
3212
- });
3213
- }
3214
- /**
3215
- * Read the selected provider for an exclusive hook from the options table.
3216
- */
3217
- async getExclusiveHookSelection(hookName) {
3218
- return new OptionsRepository(this.options.db).get(`${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`);
3219
- }
3220
- /**
3221
- * Set the selected provider for an exclusive hook in the options table.
3222
- * Pass null to clear the selection.
3223
- */
3224
- async setExclusiveHookSelection(hookName, pluginId) {
3225
- const optionsRepo = new OptionsRepository(this.options.db);
3226
- const key = `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`;
3227
- if (pluginId === null) {
3228
- await optionsRepo.delete(key);
3229
- this.hookPipeline?.clearExclusiveSelection(hookName);
3230
- return;
3231
- }
3232
- const entry = this.plugins.get(pluginId);
3233
- if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
3234
- if (entry.state !== "active") throw new Error(`Plugin "${pluginId}" is not active`);
3235
- await optionsRepo.set(key, pluginId);
3236
- this.hookPipeline?.setExclusiveSelection(hookName, pluginId);
3237
- }
3238
- /**
3239
- * Resolution algorithm for exclusive hooks.
3240
- *
3241
- * Delegates to the shared resolveExclusiveHooks() function.
3242
- * See hooks.ts for the full algorithm description.
3243
- */
3244
- async resolveExclusiveHooks(preferredHints) {
3245
- this.ensureInitialized();
3246
- const optionsRepo = new OptionsRepository(this.options.db);
3247
- await resolveExclusiveHooks({
3248
- pipeline: this.hookPipeline,
3249
- isActive: (pluginId) => this.isActive(pluginId),
3250
- getOption: (key) => optionsRepo.get(key),
3251
- getOptions: (keys) => optionsRepo.getMany(keys),
3252
- setOption: (key, value) => optionsRepo.set(key, value),
3253
- deleteOption: async (key) => {
3254
- await optionsRepo.delete(key);
3255
- },
3256
- preferredHints
3257
- });
3258
- }
3259
- /**
3260
- * Get all exclusive hooks with their providers and current selections.
3261
- * Used by the admin API.
3262
- */
3263
- async getExclusiveHooksInfo() {
3264
- this.ensureInitialized();
3265
- const exclusiveHookNames = this.hookPipeline.getRegisteredExclusiveHooks();
3266
- const result = [];
3267
- for (const hookName of exclusiveHookNames) {
3268
- const providers = this.hookPipeline.getExclusiveHookProviders(hookName);
3269
- const selection = await this.getExclusiveHookSelection(hookName);
3270
- result.push({
3271
- hookName,
3272
- providers,
3273
- selectedPluginId: selection
3274
- });
3275
- }
3276
- return result;
3277
- }
3278
- /**
3279
- * Initialize or reinitialize the hook pipeline and route registry
3280
- */
3281
- ensureInitialized() {
3282
- if (this.initialized) return;
3283
- const activePlugins = this.getActivePlugins();
3284
- this.hookPipeline = new HookPipeline(activePlugins, this.factoryOptions);
3285
- this.routeRegistry = new PluginRouteRegistry(this.factoryOptions);
3286
- for (const plugin of activePlugins) this.routeRegistry.register(plugin);
3287
- this.initialized = true;
3288
- }
3289
- /**
3290
- * Force reinitialization (useful after plugin state changes)
3291
- */
3292
- reinitialize() {
3293
- this.initialized = false;
3294
- this.ensureInitialized();
3295
- }
3296
- /**
3297
- * Delete all cron tasks for a plugin.
3298
- * Used during uninstall.
3299
- */
3300
- async deleteCronTasks(pluginId) {
3301
- try {
3302
- await sql`
3303
- DELETE FROM _emdash_cron_tasks
3304
- WHERE plugin_id = ${pluginId}
3305
- `.execute(this.options.db);
3306
- } catch {}
3307
- }
3308
- };
3309
- /**
3310
- * Create a plugin manager
3311
- */
3312
- function createPluginManager(options) {
3313
- return new PluginManager(options);
3314
- }
3315
-
3316
- //#endregion
3317
- //#region src/plugins/scheduler/node.ts
3318
- /** Minimum polling interval (ms) — prevents tight loops if next_run_at is in the past */
3319
- const MIN_INTERVAL_MS = 1e3;
3320
- /**
3321
- * Maximum polling interval (ms). Each wake runs the maintenance pass — stale
3322
- * lock recovery *and* the scheduled-publishing sweep + system cleanup. The cap
3323
- * is the worst-case latency for scheduled content when no plugin cron task is
3324
- * due sooner (`getNextDueTime()` only knows about cron tasks, not content
3325
- * `scheduled_at`). Held at 60s so Node publish latency matches the Cloudflare
3326
- * Cron Trigger cadence (`* * * * *`) rather than lagging up to five minutes.
3327
- */
3328
- const MAX_INTERVAL_MS = 60 * 1e3;
3329
- var NodeCronScheduler = class {
3330
- timer = null;
3331
- running = false;
3332
- systemCleanup = null;
3333
- constructor(executor) {
3334
- this.executor = executor;
3335
- }
3336
- setSystemCleanup(fn) {
3337
- this.systemCleanup = fn;
3338
- }
3339
- start() {
3340
- this.running = true;
3341
- this.arm();
3342
- }
3343
- stop() {
3344
- this.running = false;
3345
- if (this.timer) {
3346
- clearTimeout(this.timer);
3347
- this.timer = null;
3348
- }
3349
- }
3350
- reschedule() {
3351
- if (!this.running) return;
3352
- if (this.timer) {
3353
- clearTimeout(this.timer);
3354
- this.timer = null;
3355
- }
3356
- this.arm();
3357
- }
3358
- arm() {
3359
- if (!this.running) return;
3360
- this.executor.getNextDueTime().then((nextDue) => {
3361
- if (!this.running) return void 0;
3362
- let delayMs;
3363
- if (nextDue) {
3364
- const dueAt = new Date(nextDue).getTime();
3365
- delayMs = Math.max(dueAt - Date.now(), MIN_INTERVAL_MS);
3366
- delayMs = Math.min(delayMs, MAX_INTERVAL_MS);
3367
- } else delayMs = MAX_INTERVAL_MS;
3368
- this.timer = setTimeout(() => {
3369
- if (!this.running) return;
3370
- this.executeTick();
3371
- }, delayMs);
3372
- if (this.timer && typeof this.timer === "object" && "unref" in this.timer) this.timer.unref();
3373
- }).catch((error) => {
3374
- console.error("[cron:node] Failed to get next due time:", error);
3375
- if (this.running) {
3376
- this.timer = setTimeout(() => this.arm(), MAX_INTERVAL_MS);
3377
- if (this.timer && typeof this.timer === "object" && "unref" in this.timer) this.timer.unref();
3378
- }
3379
- });
3380
- }
3381
- executeTick() {
3382
- if (!this.running) return;
3383
- const tasks = [this.executor.tick(), this.executor.recoverStaleLocks()];
3384
- if (this.systemCleanup) tasks.push(this.systemCleanup());
3385
- Promise.allSettled(tasks).then((results) => {
3386
- for (const r of results) if (r.status === "rejected") console.error("[cron:node] Tick task failed:", r.reason);
3387
- }).finally(() => {
3388
- if (this.running) this.arm();
3389
- });
3390
- }
3391
- };
3392
-
3393
- //#endregion
3394
- //#region src/plugins/sandbox/noop.ts
3395
- /**
3396
- * Error thrown when attempting to use sandboxing on an unsupported platform.
3397
- */
3398
- var SandboxNotAvailableError = class extends Error {
3399
- constructor() {
3400
- 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.");
3401
- this.name = "SandboxNotAvailableError";
3402
- }
3403
- };
3404
- /**
3405
- * No-op sandbox runner for platforms without isolation support.
3406
- *
3407
- * - `isAvailable()` returns false
3408
- * - `load()` throws SandboxNotAvailableError
3409
- * - `terminateAll()` is a no-op
3410
- *
3411
- * This is the default runner when no platform adapter is configured.
3412
- */
3413
- var NoopSandboxRunner = class {
3414
- /**
3415
- * Always returns false - sandboxing is not available.
3416
- */
3417
- isAvailable() {
3418
- return false;
3419
- }
3420
- /**
3421
- * Always returns false - no sandbox runtime to be healthy.
3422
- */
3423
- isHealthy() {
3424
- return false;
3425
- }
3426
- /**
3427
- * Always throws - can't load sandboxed plugins without isolation.
3428
- */
3429
- async load(_manifest, _code) {
3430
- throw new SandboxNotAvailableError();
3431
- }
3432
- /**
3433
- * No-op - sandboxing not available, email callback is irrelevant.
3434
- */
3435
- setEmailSend() {}
3436
- /**
3437
- * No-op - nothing to terminate.
3438
- */
3439
- async terminateAll() {}
3440
- };
3441
- /**
3442
- * Create a no-op sandbox runner.
3443
- * This is used as the default when no platform adapter is configured.
3444
- */
3445
- function createNoopSandboxRunner(_options) {
3446
- return new NoopSandboxRunner();
3447
- }
3448
-
3449
- //#endregion
3450
- //#region src/plugins/sandbox/types.ts
3451
- const SANDBOX_ROUTE_ERROR_DEFINITIONS = {
3452
- MEDIA_USAGE_ACTIVATION_IN_PROGRESS: {
3453
- message: "Media usage activation is in progress",
3454
- status: 503
3455
- },
3456
- MEDIA_USAGE_ACTIVATION_CHECK_FAILED: {
3457
- message: "Unable to verify media usage activation state",
3458
- status: 503
3459
- }
3460
- };
3461
- function isRecord$1(value) {
3462
- return typeof value === "object" && value !== null;
3463
- }
3464
- function isSandboxRouteErrorCode(value) {
3465
- return typeof value === "string" && value in SANDBOX_ROUTE_ERROR_DEFINITIONS;
3466
- }
3467
- function getSandboxRouteErrorDetails(error) {
3468
- if (!isRecord$1(error)) return null;
3469
- const propertyCode = isSandboxRouteErrorCode(error.code) ? error.code : null;
3470
- const nameCode = error instanceof Error && isSandboxRouteErrorCode(error.name) ? error.name : null;
3471
- if (propertyCode && nameCode && propertyCode !== nameCode) return null;
3472
- const code = propertyCode ?? nameCode;
3473
- if (!code || error.status !== void 0 && error.status !== 503) return null;
3474
- return {
3475
- code,
3476
- ...SANDBOX_ROUTE_ERROR_DEFINITIONS[code]
3477
- };
3478
- }
3479
- function createSandboxRouteError(code) {
3480
- const details = {
3481
- code,
3482
- ...SANDBOX_ROUTE_ERROR_DEFINITIONS[code]
3483
- };
3484
- return Object.assign(new Error(details.message), details, { name: code });
3485
- }
3486
- function createSandboxRouteErrorEnvelope(error) {
3487
- const details = getSandboxRouteErrorDetails(error);
3488
- return details ? {
3489
- __emdashSandboxRouteError: true,
3490
- error: details
3491
- } : null;
3492
- }
3493
- function getSandboxRouteErrorEnvelope(value) {
3494
- if (!isRecord$1(value) || value.__emdashSandboxRouteError !== true) return null;
3495
- const details = getSandboxRouteErrorDetails(value.error);
3496
- return details ? {
3497
- __emdashSandboxRouteError: true,
3498
- error: details
3499
- } : null;
3500
- }
3501
- /**
3502
- * Error thrown when the sandbox runtime is unavailable.
3503
- * This happens when the sidecar process has crashed or hasn't started.
3504
- */
3505
- var SandboxUnavailableError = class extends Error {
3506
- constructor(pluginId, reason) {
3507
- super(`Plugin sandbox unavailable for ${pluginId}: ${reason}`);
3508
- this.name = "SandboxUnavailableError";
3509
- }
3510
- };
3511
-
3512
- //#endregion
3513
- //#region src/plugins/sandbox/hook-result.ts
3514
- /** Current wire-format version for sandbox hook result envelopes. */
3515
- const SANDBOX_HOOK_RESULT_VERSION = 1;
3516
- /** Maximum editor-facing rejection reason accepted from a sandboxed plugin. */
3517
- const MAX_SANDBOX_SAVE_REJECTION_REASON_LENGTH = 500;
3518
- function isRecord(value) {
3519
- return typeof value === "object" && value !== null && !Array.isArray(value);
3520
- }
3521
- /** Validate and normalize an untrusted value returned across the sandbox boundary. */
3522
- function inspectSandboxHookResult(value) {
3523
- if (!isRecord(value) || !Object.hasOwn(value, "__emdashSandboxHookResult")) return { kind: "value" };
3524
- 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" };
3525
- const reason = value.error.reason.trim();
3526
- if (reason.length === 0 || reason.length > MAX_SANDBOX_SAVE_REJECTION_REASON_LENGTH) return { kind: "malformed" };
3527
- return {
3528
- kind: "error",
3529
- error: {
3530
- code: "SAVE_REJECTED",
3531
- reason
3532
- }
3533
- };
3534
- }
3535
-
3536
- //#endregion
3537
- //#region src/comments/ranking.ts
3538
- /**
3539
- * Comment ranking utilities (Tier 1 of the best-in-class comments RFC).
3540
- *
3541
- * Wilson score lower-bound (95% confidence) — the same primitive Reddit uses
3542
- * for its "Best" comment sort. Ranks by the statistical lower bound of the
3543
- * positive-reaction proportion rather than the raw count, so a comment with a
3544
- * couple of reactions can't outrank a heavily-reacted one until it earns
3545
- * confidence, and a late-but-popular comment still rises (submission time is
3546
- * irrelevant).
3547
- *
3548
- * Positive-only reactions (the recommended default) degrade gracefully: with
3549
- * `down = 0` the score still increases monotonically with `up` while penalising
3550
- * low-sample comments — wilson(1,0) ≈ 0.21, wilson(10,0) ≈ 0.73,
3551
- * wilson(200,0) ≈ 0.98.
3552
- */
3553
- /** z for a 95% two-sided confidence interval. */
3554
- const DEFAULT_Z = 1.96;
3555
- /** Reactions that count against a comment when both signals are in use. */
3556
- const NEGATIVE_REACTIONS = new Set(["dislike", "down"]);
3557
- /**
3558
- * Wilson score lower bound of the positive proportion.
3559
- *
3560
- * @param up count of positive reactions
3561
- * @param down count of negative reactions
3562
- * @returns a score in [0, 1]; 0 when there are no reactions
3563
- */
3564
- function wilsonLowerBound(up, down, z = DEFAULT_Z) {
3565
- const n = up + down;
3566
- if (n <= 0) return 0;
3567
- const phat = up / n;
3568
- const z2 = z * z;
3569
- const denom = 1 + z2 / n;
3570
- return (phat + z2 / (2 * n) - z * Math.sqrt((phat * (1 - phat) + z2 / (4 * n)) / n)) / denom;
3571
- }
3572
- /**
3573
- * Reduce a per-reaction count map to a single rank score via the Wilson
3574
- * lower bound. Any reaction not in {@link NEGATIVE_REACTIONS} is treated as
3575
- * positive, so the positive-only default (just `like`) works without special
3576
- * casing.
3577
- */
3578
- function reactionScore(counts) {
3579
- let up = 0;
3580
- let down = 0;
3581
- for (const [reaction, count] of Object.entries(counts)) if (NEGATIVE_REACTIONS.has(reaction)) down += count;
3582
- else up += count;
3583
- return wilsonLowerBound(up, down);
3584
- }
3585
-
3586
- //#endregion
3587
- //#region src/comments/query.ts
3588
- /**
3589
- * Get approved comments for a content item.
3590
- *
3591
- * @example
3592
- * ```ts
3593
- * import { getComments } from "emdash";
3594
- *
3595
- * const { items, total } = await getComments({
3596
- * collection: "posts",
3597
- * contentId: post.id,
3598
- * threaded: true,
3599
- * });
3600
- * ```
3601
- */
3602
- async function getComments(options) {
3603
- const sort = options.sort ?? "oldest";
3604
- const withReactions = options.reactions || sort === "best";
3605
- const threaded = options.threaded ? "t" : "f";
3606
- return cachedQuery({
3607
- namespace: CacheNamespace.COMMENTS,
3608
- key: `comments:${options.collection}:${options.contentId}:${threaded}:${withReactions ? "r" : "n"}:${sort}`,
3609
- load: async () => {
3610
- return getCommentsWithDb(await getDb(), options);
3611
- }
3612
- });
3613
- }
3614
- /**
3615
- * Get approved comments with an explicit db handle.
3616
- *
3617
- * @internal Use `getComments()` in templates. This variant is for routes
3618
- * that already have a database handle.
3619
- */
3620
- async function getCommentsWithDb(db, options) {
3621
- const repo = new CommentRepository(db);
3622
- const total = await repo.countByContent(options.collection, options.contentId, "approved");
3623
- const result = await repo.findByContent(options.collection, options.contentId, {
3624
- status: "approved",
3625
- limit: 500
3626
- });
3627
- const items = options.threaded ? CommentRepository.assembleThreads(result.items).map((c) => CommentRepository.toPublicComment(c)) : result.items.map((c) => CommentRepository.toPublicComment(c));
3628
- if (options.reactions || options.sort === "best") {
3629
- await attachReactions(db, items);
3630
- if (options.sort === "best") sortByBest(items);
3631
- }
3632
- return {
3633
- items,
3634
- total
3635
- };
3636
- }
3637
- /**
3638
- * Attach aggregate reaction counts to a list of public comments (and their
3639
- * replies), in a single batched query.
3640
- */
3641
- async function attachReactions(db, items) {
3642
- const ids = [];
3643
- for (const comment of items) {
3644
- ids.push(comment.id);
3645
- if (comment.replies) for (const reply of comment.replies) ids.push(reply.id);
3646
- }
3647
- if (ids.length === 0) return;
3648
- const counts = await new CommentReactionRepository(db).countsForComments(ids);
3649
- const assign = (comment) => {
3650
- const reactions = counts.get(comment.id);
3651
- if (reactions) comment.reactions = reactions;
3652
- };
3653
- for (const comment of items) {
3654
- assign(comment);
3655
- comment.replies?.forEach(assign);
3656
- }
3657
- }
3658
- /**
3659
- * Sort top-level comments by Wilson-scored reactions (descending), tie-broken
3660
- * by oldest-first to keep ordering stable.
3661
- */
3662
- function sortByBest(items) {
3663
- items.sort((a, b) => {
3664
- const scoreDelta = reactionScore(b.reactions ?? {}) - reactionScore(a.reactions ?? {});
3665
- if (scoreDelta !== 0) return scoreDelta;
3666
- if (a.createdAt < b.createdAt) return -1;
3667
- if (a.createdAt > b.createdAt) return 1;
3668
- return 0;
3669
- });
3670
- }
3671
- /**
3672
- * Get the count of approved comments for a content item.
3673
- *
3674
- * @example
3675
- * ```ts
3676
- * import { getCommentCount } from "emdash";
3677
- *
3678
- * const count = await getCommentCount("posts", post.id);
3679
- * ```
3680
- */
3681
- async function getCommentCount(collection, contentId) {
3682
- return getCommentCountWithDb(await getDb(), collection, contentId);
3683
- }
3684
- /**
3685
- * Get comment count with an explicit db handle.
3686
- *
3687
- * @internal Use `getCommentCount()` in templates.
3688
- */
3689
- async function getCommentCountWithDb(db, collection, contentId) {
3690
- return new CommentRepository(db).countByContent(collection, contentId, "approved");
3691
- }
3692
-
3693
- //#endregion
3694
- //#region src/menus/index.ts
3695
- /**
3696
- * Get a menu by name with resolved URLs.
3697
- *
3698
- * @example
3699
- * ```ts
3700
- * const menu = await getMenu("primary");
3701
- * const menuEs = await getMenu("primary", { locale: "es" });
3702
- * ```
3703
- */
3704
- function getMenu(name, options = {}) {
3705
- const locale = resolveLocale(options.locale);
3706
- return requestCached(`menu:${name}:${locale ?? "*"}`, () => cachedQuery({
3707
- namespace: CacheNamespace.MENUS,
3708
- key: `${name}:${locale ?? "*"}`,
3709
- load: async () => {
3710
- return getMenuWithDb(name, await getDb(), { locale });
3711
- }
3712
- }));
3713
- }
3714
- /**
3715
- * Get menu by name with resolved URLs (with explicit db). Internal helper for
3716
- * admin routes that already have a database handle.
3717
- */
3718
- async function getMenuWithDb(name, db, options = {}) {
3719
- const chain = resolveLocaleChain(options.locale);
3720
- const selectMenu = () => db.selectFrom("_emdash_menus").selectAll().where("name", "=", name);
3721
- let menuRow;
3722
- if (chain.length === 0) menuRow = await selectMenu().orderBy("locale", "asc").executeTakeFirst();
3723
- else {
3724
- menuRow = void 0;
3725
- for (const locale of chain) {
3726
- menuRow = await selectMenu().where("locale", "=", locale).executeTakeFirst();
3727
- if (menuRow) break;
3728
- }
3729
- }
3730
- if (!menuRow) return null;
3731
- const items = await buildMenuTree(await db.selectFrom("_emdash_menu_items").selectAll().$castTo().where("menu_id", "=", menuRow.id).orderBy("sort_order", "asc").execute(), db, menuRow.locale);
3732
- return {
3733
- id: menuRow.id,
3734
- name: menuRow.name,
3735
- label: menuRow.label,
3736
- items,
3737
- locale: menuRow.locale,
3738
- translationGroup: menuRow.translation_group
3739
- };
3740
- }
3741
- /**
3742
- * Get all menus (without items, locale-filtered — for admin list / site nav
3743
- * summaries). When no locale is configured, returns menus across all locales.
3744
- */
3745
- async function getMenus(options = {}) {
3746
- return getMenusWithDb(await getDb(), options);
3747
- }
3748
- /**
3749
- * Get all menus (with explicit db)
3750
- *
3751
- * @internal Use `getMenus()` in templates. This variant is for admin routes
3752
- * that already have a database handle.
3753
- */
3754
- async function getMenusWithDb(db, options = {}) {
3755
- const locale = resolveLocale(options.locale);
3756
- let query = db.selectFrom("_emdash_menus").select([
3757
- "id",
3758
- "name",
3759
- "label",
3760
- "locale"
3761
- ]).orderBy("name", "asc");
3762
- if (locale !== void 0) query = query.where("locale", "=", locale);
3763
- return query.execute();
3764
- }
3765
- /**
3766
- * Get a menu by name with a Workers edge-cache hint.
3767
- *
3768
- * Use the returned `cacheHint` with `Astro.cache.set()` so pages that render
3769
- * this menu can be purged automatically when the menu is edited.
3770
- */
3771
- async function getMenuWithCacheHint(name, options = {}) {
3772
- return {
3773
- data: await getMenu(name, options),
3774
- cacheHint: { tags: [menuTag(name)] }
3775
- };
3776
- }
3777
- /**
3778
- * Build a hierarchical menu tree from a flat list of items. Items are
3779
- * resolved against the given `locale` so references land on the right
3780
- * per-locale content rows.
3781
- */
3782
- async function buildMenuTree(items, db, locale) {
3783
- const contentReferences = collectContentReferences(items);
3784
- const taxonomyReferences = new Set(items.flatMap((item) => item.type === "taxonomy" && item.reference_id ? [item.reference_id] : []));
3785
- const [urlPatterns, contentLookup, taxonomyLookup] = await Promise.all([
3786
- contentReferences.size > 0 ? getCollectionUrlPatterns(db, new Set(contentReferences.keys())) : /* @__PURE__ */ new Map(),
3787
- resolveContentReferences(db, contentReferences, locale),
3788
- resolveTaxonomyReferences(db, taxonomyReferences, locale)
3789
- ]);
3790
- const validItems = items.map((item) => resolveMenuItem(item, urlPatterns, contentLookup, taxonomyLookup)).filter((item) => item !== null);
3791
- const itemMap = /* @__PURE__ */ new Map();
3792
- const rootItems = [];
3793
- for (const item of validItems) itemMap.set(item.id, {
3794
- ...item,
3795
- children: []
3796
- });
3797
- for (const item of items) {
3798
- const menuItem = itemMap.get(item.id);
3799
- if (!menuItem) continue;
3800
- if (item.parent_id) {
3801
- const parent = itemMap.get(item.parent_id);
3802
- if (parent) parent.children.push(menuItem);
3803
- else rootItems.push(menuItem);
3804
- } else rootItems.push(menuItem);
3805
- }
3806
- return rootItems;
3807
- }
3808
- function collectContentReferences(items) {
3809
- const references = /* @__PURE__ */ new Map();
3810
- for (const item of items) {
3811
- const reference = getContentReference(item);
3812
- if (!reference) continue;
3813
- let ids = references.get(reference.collection);
3814
- if (!ids) {
3815
- ids = /* @__PURE__ */ new Set();
3816
- references.set(reference.collection, ids);
3817
- }
3818
- ids.add(reference.id);
3819
- }
3820
- return references;
3821
- }
3822
- function getContentReference(item) {
3823
- if (item.type === "page" || item.type === "post") {
3824
- if (!item.reference_id) return null;
3825
- return {
3826
- collection: item.reference_collection || `${item.type}s`,
3827
- id: item.reference_id
3828
- };
3829
- }
3830
- if (item.type === "collection") {
3831
- if (!item.reference_collection || !item.reference_id) return null;
3832
- return {
3833
- collection: item.reference_collection,
3834
- id: item.reference_id
3835
- };
3836
- }
3837
- if (item.type !== "custom" && item.type !== "taxonomy" && item.reference_collection && item.reference_id) return {
3838
- collection: item.reference_collection,
3839
- id: item.reference_id
3840
- };
3841
- return null;
3842
- }
3843
- /**
3844
- * Look up the `url_pattern` for a set of collection slugs, request-cached so
3845
- * a page rendering several menus (header, footer, ...) only pays for the
3846
- * lookup once per distinct slug set. Callers must treat the returned map as
3847
- * read-only — it is shared across cache hits within the request.
3848
- */
3849
- function getCollectionUrlPatterns(db, collectionSlugs) {
3850
- return requestCached(`menu-collection-patterns:${[...collectionSlugs].toSorted().join(",")}`, async () => {
3851
- const rows = await db.selectFrom("_emdash_collections").select(["slug", "url_pattern"]).where("slug", "in", [...collectionSlugs]).execute();
3852
- const urlPatterns = /* @__PURE__ */ new Map();
3853
- for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
3854
- return urlPatterns;
3855
- });
3856
- }
3857
- /**
3858
- * Resolve a single menu item's URL. `reference_id` is a translation_group
3859
- * (migration 036 remapped all existing references); we look it up against
3860
- * the per-locale ec_* row or per-locale taxonomy row.
3861
- */
3862
- function resolveMenuItem(item, urlPatterns, contentLookup, taxonomyLookup) {
3863
- let url;
3864
- switch (item.type) {
3865
- case "custom":
3866
- url = item.custom_url || "#";
3867
- break;
3868
- case "page":
3869
- case "post":
3870
- url = resolveContentUrl(item.reference_collection || `${item.type}s`, item.reference_id, urlPatterns, contentLookup);
3871
- if (url === null) return null;
3872
- break;
3873
- case "taxonomy":
3874
- url = resolveTaxonomyUrl(item.reference_id, taxonomyLookup);
3875
- if (url === null) return null;
3876
- break;
3877
- case "collection":
3878
- if (!item.reference_collection) return null;
3879
- if (item.reference_id) {
3880
- url = resolveContentUrl(item.reference_collection, item.reference_id, urlPatterns, contentLookup);
3881
- if (url === null) return null;
3882
- } else url = `/${item.reference_collection}/`;
3883
- break;
3884
- default: if (item.reference_collection && item.reference_id) {
3885
- url = resolveContentUrl(item.reference_collection, item.reference_id, urlPatterns, contentLookup);
3886
- if (url === null) return null;
3887
- } else url = "#";
3888
- }
3889
- return {
3890
- id: item.id,
3891
- label: item.label,
3892
- url: sanitizeHref(url),
3893
- target: item.target || void 0,
3894
- titleAttr: item.title_attr || void 0,
3895
- cssClasses: item.css_classes || void 0,
3896
- children: []
3897
- };
3898
- }
3899
- const SLUG_PLACEHOLDER = /\{slug\}/g;
3900
- const ID_PLACEHOLDER = /\{id\}/g;
3901
- /**
3902
- * Interpolate a URL pattern with entry data
3903
- *
3904
- * Replaces `{slug}` and `{id}` placeholders.
3905
- */
3906
- function interpolateUrlPattern(pattern, slug, id) {
3907
- return pattern.replace(SLUG_PLACEHOLDER, slug).replace(ID_PLACEHOLDER, id);
3908
- }
3909
- async function resolveContentReferences(db, references, locale) {
3910
- const entries = await Promise.all(Array.from(references, async ([collection, referenceGroups]) => {
3911
- const lookup = /* @__PURE__ */ new Map();
3912
- const localized = /* @__PURE__ */ new Map();
3913
- try {
3914
- validateIdentifier(collection, "menu item collection");
3915
- for (const batch of chunks([...referenceGroups], SQL_BATCH_SIZE)) {
3916
- const result = await sql`
3917
- SELECT id, slug, locale, translation_group
3918
- FROM ${sql.ref(`ec_${collection}`)}
3919
- WHERE translation_group IN (${sql.join(batch)})
3920
- `.execute(db);
3921
- for (const row of result.rows) if (shouldPreferLocalizedRow(row, localized.get(row.translation_group), locale)) localized.set(row.translation_group, row);
3922
- }
3923
- for (const [referenceGroup, row] of localized) lookup.set(referenceGroup, {
3924
- id: row.id,
3925
- slug: row.slug
3926
- });
3927
- const unresolved = [...referenceGroups].filter((id) => !lookup.has(id));
3928
- for (const batch of chunks(unresolved, SQL_BATCH_SIZE)) {
3929
- const result = await sql`
3930
- SELECT id, slug FROM ${sql.ref(`ec_${collection}`)}
3931
- WHERE id IN (${sql.join(batch)})
3932
- `.execute(db);
3933
- for (const row of result.rows) lookup.set(row.id, row);
3934
- }
3935
- } catch (error) {
3936
- console.error(`Failed to resolve content URLs for ${collection}:`, error);
3937
- }
3938
- return [collection, lookup];
3939
- }));
3940
- return new Map(entries);
3941
- }
3942
- function shouldPreferLocalizedRow(candidate, existing, locale) {
3943
- if (!existing) return true;
3944
- if (candidate.locale === locale) return existing.locale !== locale || candidate.id < existing.id;
3945
- if (existing.locale === locale) return false;
3946
- return candidate.locale < existing.locale || candidate.locale === existing.locale && candidate.id < existing.id;
3947
- }
3948
- /**
3949
- * Resolve the URL for a content reference. `referenceGroup` is the content
3950
- * row's translation_group; we look up the row in the requested locale
3951
- * (falling back to the source if no translation exists so the menu link is
3952
- * still clickable).
3953
- */
3954
- function resolveContentUrl(collection, referenceGroup, urlPatterns, contentLookup) {
3955
- if (!referenceGroup) return null;
3956
- const row = contentLookup.get(collection)?.get(referenceGroup);
3957
- if (!row) return null;
3958
- const pattern = urlPatterns.get(collection);
3959
- if (pattern) return interpolateUrlPattern(pattern, row.slug, row.id);
3960
- return `/${collection}/${row.slug}`;
3961
- }
3962
- async function resolveTaxonomyReferences(db, referenceGroups, locale) {
3963
- const lookup = /* @__PURE__ */ new Map();
3964
- const localized = /* @__PURE__ */ new Map();
3965
- try {
3966
- for (const batch of chunks([...referenceGroups], SQL_BATCH_SIZE)) {
3967
- const rows = await db.selectFrom("taxonomies").select([
3968
- "id",
3969
- "name",
3970
- "slug",
3971
- "locale",
3972
- "translation_group"
3973
- ]).where("translation_group", "in", batch).$narrowType().execute();
3974
- for (const row of rows) if (shouldPreferLocalizedRow(row, localized.get(row.translation_group), locale)) localized.set(row.translation_group, row);
3975
- }
3976
- for (const [referenceGroup, row] of localized) lookup.set(referenceGroup, {
3977
- name: row.name,
3978
- slug: row.slug
3979
- });
3980
- const unresolved = [...referenceGroups].filter((id) => !lookup.has(id));
3981
- for (const batch of chunks(unresolved, SQL_BATCH_SIZE)) {
3982
- const rows = await db.selectFrom("taxonomies").select([
3983
- "id",
3984
- "name",
3985
- "slug"
3986
- ]).where("id", "in", batch).execute();
3987
- for (const row of rows) lookup.set(row.id, {
3988
- name: row.name,
3989
- slug: row.slug
3990
- });
3991
- }
3992
- } catch (error) {
3993
- console.error("Failed to resolve taxonomy URLs:", error);
3994
- }
3995
- return lookup;
3996
- }
3997
- /**
3998
- * Resolve URL for a taxonomy term reference. `referenceGroup` is the term's
3999
- * translation_group; we pick the row in the active locale (or fall back).
4000
- */
4001
- function resolveTaxonomyUrl(referenceGroup, taxonomyLookup) {
4002
- if (!referenceGroup) return null;
4003
- const taxonomy = taxonomyLookup.get(referenceGroup);
4004
- if (!taxonomy) return null;
4005
- return `/${taxonomy.name}/${taxonomy.slug}`;
4006
- }
4007
-
4008
- //#endregion
4009
- export { resolveExclusiveHooks as A, parseRouteInput as C, isContentSaveRejection as D, ContentSaveRejectedError as E, portableText as F, reference as I, file as L, portableTextToProsemirror as M, prosemirrorToPortableText as N, HookPipeline as O, portableTextIdentityExtensions as P, image as R, buildRouteMeta as S, EmailPipeline as T, NodeCronScheduler as _, getComments as a, PluginRouteError as b, inspectSandboxHookResult as c, createSandboxRouteErrorEnvelope as d, getSandboxRouteErrorDetails as f, createNoopSandboxRunner as g, SandboxNotAvailableError as h, getCommentCount as i, definePlugin as j, createHookPipeline as k, SandboxUnavailableError as l, NoopSandboxRunner as m, getMenuWithCacheHint as n, MAX_SANDBOX_SAVE_REJECTION_REASON_LENGTH as o, getSandboxRouteErrorEnvelope as p, getMenus as r, SANDBOX_HOOK_RESULT_VERSION as s, getMenu as t, createSandboxRouteError as u, PluginManager as v, toRouteCallerInfo as w, PluginRouteRegistry as x, createPluginManager as y, AuditRepository as z };
4010
- //# sourceMappingURL=menus-6qSwTNxG.mjs.map