emdash 0.21.0 → 0.22.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 (620) hide show
  1. package/dist/{adapters-BxSmgtbF.d.mts → adapters-hUy8vOB-.d.mts} +1 -1
  2. package/dist/{adapters-BxSmgtbF.d.mts.map → adapters-hUy8vOB-.d.mts.map} +1 -1
  3. package/dist/after-B1IIdH3Y.mjs +29 -0
  4. package/dist/after-B1IIdH3Y.mjs.map +1 -0
  5. package/dist/{allowed-origins-BqC8cul8.mjs → allowed-origins-DjlhyfNN.mjs} +2 -2
  6. package/dist/{allowed-origins-BqC8cul8.mjs.map → allowed-origins-DjlhyfNN.mjs.map} +1 -1
  7. package/dist/api/route-utils.d.mts +3 -3
  8. package/dist/api/route-utils.mjs +20 -19
  9. package/dist/api/route-utils.mjs.map +1 -1
  10. package/dist/api/schemas/index.d.mts +2 -2
  11. package/dist/api/schemas/index.mjs +4 -4
  12. package/dist/{api-DxjIV2o8.mjs → api-BDuM4bEy.mjs} +31 -25
  13. package/dist/api-BDuM4bEy.mjs.map +1 -0
  14. package/dist/{api-tokens-BFFkB0jB.mjs → api-tokens-ViYKeIp9.mjs} +13 -3
  15. package/dist/api-tokens-ViYKeIp9.mjs.map +1 -0
  16. package/dist/{apply-CLjxheyb.mjs → apply-wDqQoxX4.mjs} +18 -18
  17. package/dist/{apply-CLjxheyb.mjs.map → apply-wDqQoxX4.mjs.map} +1 -1
  18. package/dist/astro/image-endpoint.d.mts +8 -0
  19. package/dist/astro/image-endpoint.d.mts.map +1 -0
  20. package/dist/astro/image-endpoint.mjs +55 -0
  21. package/dist/astro/image-endpoint.mjs.map +1 -0
  22. package/dist/astro/index.d.mts +36 -11
  23. package/dist/astro/index.d.mts.map +1 -1
  24. package/dist/astro/index.mjs +114 -24
  25. package/dist/astro/index.mjs.map +1 -1
  26. package/dist/astro/middleware/auth.d.mts +9 -9
  27. package/dist/astro/middleware/auth.d.mts.map +1 -1
  28. package/dist/astro/middleware/auth.mjs +12 -10
  29. package/dist/astro/middleware/auth.mjs.map +1 -1
  30. package/dist/astro/middleware/redirect.mjs +6 -6
  31. package/dist/astro/middleware/request-context.mjs +5 -5
  32. package/dist/astro/middleware/setup.mjs +1 -1
  33. package/dist/astro/middleware.d.mts +1 -1
  34. package/dist/astro/middleware.d.mts.map +1 -1
  35. package/dist/astro/middleware.mjs +153 -79
  36. package/dist/astro/middleware.mjs.map +1 -1
  37. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +7 -7
  38. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +7 -7
  39. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  40. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  41. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  42. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +10 -10
  43. package/dist/astro/routes/api/admin/byline-fields/index.mjs +10 -10
  44. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +10 -10
  45. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -16
  46. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -16
  48. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  49. package/dist/astro/routes/api/admin/bylines/index.mjs +17 -16
  50. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  51. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +15 -13
  52. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/comments/_id_.mjs +8 -6
  54. package/dist/astro/routes/api/admin/comments/_id_.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/comments/bulk.mjs +12 -10
  56. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  57. package/dist/astro/routes/api/admin/comments/counts.mjs +8 -6
  58. package/dist/astro/routes/api/admin/comments/counts.mjs.map +1 -1
  59. package/dist/astro/routes/api/admin/comments/index.mjs +12 -10
  60. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  61. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
  62. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
  63. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  64. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  65. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +40 -39
  66. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +40 -39
  68. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +39 -38
  70. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +39 -38
  72. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +39 -38
  74. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  75. package/dist/astro/routes/api/admin/plugins/index.mjs +39 -38
  76. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  78. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +39 -38
  79. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +39 -38
  81. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +39 -38
  83. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +39 -38
  85. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +40 -39
  87. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +39 -38
  89. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +40 -39
  91. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/updates.mjs +39 -38
  93. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  94. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +39 -38
  95. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  97. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +39 -38
  98. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  99. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  100. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  101. package/dist/astro/routes/api/admin/users/_id_/index.mjs +8 -8
  102. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
  103. package/dist/astro/routes/api/admin/users/index.mjs +7 -7
  104. package/dist/astro/routes/api/auth/dev-bypass.mjs +6 -6
  105. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  106. package/dist/astro/routes/api/auth/invite/complete.mjs +12 -12
  107. package/dist/astro/routes/api/auth/invite/index.mjs +9 -9
  108. package/dist/astro/routes/api/auth/invite/register-options.mjs +11 -11
  109. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  110. package/dist/astro/routes/api/auth/magic-link/send.mjs +11 -11
  111. package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
  112. package/dist/astro/routes/api/auth/me.mjs +8 -8
  113. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  114. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  115. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  116. package/dist/astro/routes/api/auth/passkey/_id_.mjs +7 -7
  117. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  118. package/dist/astro/routes/api/auth/passkey/options.mjs +13 -13
  119. package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -11
  120. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -12
  121. package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -12
  122. package/dist/astro/routes/api/auth/signup/complete.mjs +12 -12
  123. package/dist/astro/routes/api/auth/signup/request.mjs +11 -11
  124. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  125. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +16 -15
  126. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  127. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.d.mts +9 -0
  128. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.d.mts.map +1 -0
  129. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +179 -0
  130. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs.map +1 -0
  131. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  132. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +4 -4
  133. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +4 -4
  134. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  135. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +13 -13
  136. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +8 -8
  137. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +4 -4
  138. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  139. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  140. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +17 -15
  141. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  142. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
  143. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +4 -4
  144. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  145. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  146. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  147. package/dist/astro/routes/api/content/_collection_/trash.mjs +8 -8
  148. package/dist/astro/routes/api/dashboard.mjs +10 -8
  149. package/dist/astro/routes/api/dashboard.mjs.map +1 -1
  150. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  151. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  152. package/dist/astro/routes/api/import/probe.mjs +12 -12
  153. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
  154. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  155. package/dist/astro/routes/api/import/wordpress/execute.mjs +16 -15
  156. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  157. package/dist/astro/routes/api/import/wordpress/media.mjs +10 -10
  158. package/dist/astro/routes/api/import/wordpress/prepare.mjs +11 -11
  159. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +10 -10
  160. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  161. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +12 -12
  162. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  163. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  164. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +18 -17
  165. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  166. package/dist/astro/routes/api/manifest.mjs +5 -5
  167. package/dist/astro/routes/api/mcp.mjs +213 -46
  168. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  169. package/dist/astro/routes/api/media/_id_/confirm.mjs +8 -8
  170. package/dist/astro/routes/api/media/_id_.mjs +8 -8
  171. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
  172. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  173. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  174. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  175. package/dist/astro/routes/api/media/upload-url.mjs +10 -10
  176. package/dist/astro/routes/api/media.mjs +13 -13
  177. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +11 -9
  178. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  179. package/dist/astro/routes/api/menus/_name_/items.mjs +11 -9
  180. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  181. package/dist/astro/routes/api/menus/_name_/reorder.mjs +11 -9
  182. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  183. package/dist/astro/routes/api/menus/_name_/translations.mjs +11 -9
  184. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  185. package/dist/astro/routes/api/menus/_name_.mjs +11 -9
  186. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  187. package/dist/astro/routes/api/menus/index.mjs +11 -9
  188. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  189. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  190. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  191. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  192. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  193. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  194. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  195. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  196. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  197. package/dist/astro/routes/api/openapi.json.mjs +5 -5
  198. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +5 -5
  199. package/dist/astro/routes/api/redirects/404s/index.mjs +11 -11
  200. package/dist/astro/routes/api/redirects/404s/summary.mjs +11 -11
  201. package/dist/astro/routes/api/redirects/_id_.mjs +12 -12
  202. package/dist/astro/routes/api/redirects/index.mjs +12 -12
  203. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  204. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +4 -4
  205. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +39 -38
  206. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  207. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +39 -38
  208. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  209. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +39 -38
  210. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  211. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +39 -38
  212. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  213. package/dist/astro/routes/api/schema/collections/index.mjs +39 -38
  214. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  215. package/dist/astro/routes/api/schema/index.mjs +7 -7
  216. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +39 -38
  217. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  218. package/dist/astro/routes/api/schema/orphans/index.mjs +39 -38
  219. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  220. package/dist/astro/routes/api/search/enable.mjs +11 -11
  221. package/dist/astro/routes/api/search/index.mjs +10 -10
  222. package/dist/astro/routes/api/search/rebuild.mjs +11 -11
  223. package/dist/astro/routes/api/search/stats.mjs +7 -7
  224. package/dist/astro/routes/api/search/suggest.mjs +10 -10
  225. package/dist/astro/routes/api/sections/_slug_.mjs +10 -10
  226. package/dist/astro/routes/api/sections/index.mjs +10 -10
  227. package/dist/astro/routes/api/settings/email.mjs +6 -6
  228. package/dist/astro/routes/api/settings.mjs +16 -15
  229. package/dist/astro/routes/api/settings.mjs.map +1 -1
  230. package/dist/astro/routes/api/setup/admin-verify.mjs +13 -13
  231. package/dist/astro/routes/api/setup/admin.mjs +12 -12
  232. package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
  233. package/dist/astro/routes/api/setup/dev-bypass.mjs +29 -27
  234. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  235. package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
  236. package/dist/astro/routes/api/setup/index.mjs +29 -28
  237. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  238. package/dist/astro/routes/api/setup/status.mjs +5 -5
  239. package/dist/astro/routes/api/snapshot.d.mts.map +1 -1
  240. package/dist/astro/routes/api/snapshot.mjs +10 -9
  241. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  242. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +16 -14
  243. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  244. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +16 -14
  245. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  246. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +16 -14
  247. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  248. package/dist/astro/routes/api/taxonomies/index.mjs +16 -14
  249. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  250. package/dist/astro/routes/api/themes/preview.mjs +8 -8
  251. package/dist/astro/routes/api/typegen.mjs +6 -6
  252. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  253. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  254. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  255. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +8 -8
  256. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +11 -11
  257. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +11 -11
  258. package/dist/astro/routes/api/widget-areas/_name_.mjs +7 -7
  259. package/dist/astro/routes/api/widget-areas/index.mjs +11 -11
  260. package/dist/astro/routes/api/widget-components.mjs +4 -4
  261. package/dist/astro/routes/robots.txt.mjs +10 -9
  262. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  263. package/dist/astro/routes/sitemap-_collection_.xml.mjs +13 -12
  264. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  265. package/dist/astro/routes/sitemap.xml.mjs +11 -10
  266. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  267. package/dist/astro/types.d.mts +16 -12
  268. package/dist/astro/types.d.mts.map +1 -1
  269. package/dist/auth/providers/github.d.mts +1 -1
  270. package/dist/auth/providers/google.d.mts +1 -1
  271. package/dist/{authorize-D5gfBVU5.mjs → authorize-C0EYoUeV.mjs} +2 -2
  272. package/dist/{authorize-D5gfBVU5.mjs.map → authorize-C0EYoUeV.mjs.map} +1 -1
  273. package/dist/{base64-CqR-7kqF.mjs → base64-CmWvODNW.mjs} +1 -1
  274. package/dist/{base64-CqR-7kqF.mjs.map → base64-CmWvODNW.mjs.map} +1 -1
  275. package/dist/{byline-V_Qp1Ziw.mjs → byline-CC0WS4Gu.mjs} +17 -8
  276. package/dist/byline-CC0WS4Gu.mjs.map +1 -0
  277. package/dist/{byline-fields-B0NO1yUB.mjs → byline-fields-53i9Et0x.mjs} +3 -3
  278. package/dist/{byline-fields-B0NO1yUB.mjs.map → byline-fields-53i9Et0x.mjs.map} +1 -1
  279. package/dist/{byline-fields-nBVqK_Ff.mjs → byline-fields-DNp0HNKc.mjs} +2 -2
  280. package/dist/{byline-fields-nBVqK_Ff.mjs.map → byline-fields-DNp0HNKc.mjs.map} +1 -1
  281. package/dist/{byline-fields-CQJRIQkn.d.mts → byline-fields-jy07PZJM.d.mts} +36 -31
  282. package/dist/byline-fields-jy07PZJM.d.mts.map +1 -0
  283. package/dist/{byline-registry-DedidtqC.mjs → byline-registry-BOjqDOim.mjs} +3 -3
  284. package/dist/{byline-registry-DedidtqC.mjs.map → byline-registry-BOjqDOim.mjs.map} +1 -1
  285. package/dist/{bylines-DfGDnred.mjs → bylines-CCO1CYnH.mjs} +8 -8
  286. package/dist/{bylines-DfGDnred.mjs.map → bylines-CCO1CYnH.mjs.map} +1 -1
  287. package/dist/{bylines-B2NWnIwS.mjs → bylines-DGekMGGm.mjs} +10 -4
  288. package/dist/{bylines-B2NWnIwS.mjs.map → bylines-DGekMGGm.mjs.map} +1 -1
  289. package/dist/{cache-DTTHWD8n.mjs → cache-pXTpun6s.mjs} +3 -3
  290. package/dist/{cache-DTTHWD8n.mjs.map → cache-pXTpun6s.mjs.map} +1 -1
  291. package/dist/{challenge-store-woE0bbCf.mjs → challenge-store-C6E_kWTs.mjs} +1 -1
  292. package/dist/{challenge-store-woE0bbCf.mjs.map → challenge-store-C6E_kWTs.mjs.map} +1 -1
  293. package/dist/{chunks-BerYVuve.mjs → chunks-SyjZaYwV.mjs} +2 -2
  294. package/dist/{chunks-BerYVuve.mjs.map → chunks-SyjZaYwV.mjs.map} +1 -1
  295. package/dist/cli/index.mjs +27 -26
  296. package/dist/cli/index.mjs.map +1 -1
  297. package/dist/client/cf-access.d.mts +1 -1
  298. package/dist/client/index.d.mts +1 -1
  299. package/dist/client/index.mjs +2 -1
  300. package/dist/client/index.mjs.map +1 -1
  301. package/dist/{comment-sqQxNpN3.mjs → comment-80UyJJUR.mjs} +11 -3
  302. package/dist/comment-80UyJJUR.mjs.map +1 -0
  303. package/dist/comment-reaction-B6AdcpXw.mjs +85 -0
  304. package/dist/comment-reaction-B6AdcpXw.mjs.map +1 -0
  305. package/dist/{comments-D2hNuxNa.mjs → comments-CtPkT2tp.mjs} +3 -3
  306. package/dist/{comments-D2hNuxNa.mjs.map → comments-CtPkT2tp.mjs.map} +1 -1
  307. package/dist/{components-DYKp2gmo.mjs → components-D6ZnrzbB.mjs} +1 -1
  308. package/dist/{components-DYKp2gmo.mjs.map → components-D6ZnrzbB.mjs.map} +1 -1
  309. package/dist/{content-BIlVx-RX.mjs → content-2PTDNnoh.mjs} +22 -7
  310. package/dist/content-2PTDNnoh.mjs.map +1 -0
  311. package/dist/{context-Cm4pt1Ws.mjs → context-f1__jpzF.mjs} +11 -11
  312. package/dist/{context-Cm4pt1Ws.mjs.map → context-f1__jpzF.mjs.map} +1 -1
  313. package/dist/{cron-DdEVrQ2Y.mjs → cron-CQPxBjYs.mjs} +1 -1
  314. package/dist/{cron-DdEVrQ2Y.mjs.map → cron-CQPxBjYs.mjs.map} +1 -1
  315. package/dist/{dashboard-C-UYpps0.mjs → dashboard-BOESNcwE.mjs} +4 -4
  316. package/dist/{dashboard-C-UYpps0.mjs.map → dashboard-BOESNcwE.mjs.map} +1 -1
  317. package/dist/database/instrumentation.d.mts +19 -0
  318. package/dist/database/instrumentation.d.mts.map +1 -1
  319. package/dist/database/instrumentation.mjs +8 -1
  320. package/dist/database/instrumentation.mjs.map +1 -1
  321. package/dist/db/index.d.mts +3 -3
  322. package/dist/db/index.mjs +1 -1
  323. package/dist/db/libsql.d.mts +1 -1
  324. package/dist/db/postgres.d.mts +1 -1
  325. package/dist/db/sqlite.d.mts +1 -1
  326. package/dist/{db-errors-BluWkwGI.mjs → db-errors-CrC5w3Jz.mjs} +1 -1
  327. package/dist/{db-errors-BluWkwGI.mjs.map → db-errors-CrC5w3Jz.mjs.map} +1 -1
  328. package/dist/{default-NHGuJzQ3.mjs → default-CpBEPn6a.mjs} +1 -1
  329. package/dist/{default-NHGuJzQ3.mjs.map → default-CpBEPn6a.mjs.map} +1 -1
  330. package/dist/{device-flow-BQApWgnW.mjs → device-flow-Bn-qiDrw.mjs} +5 -5
  331. package/dist/{device-flow-BQApWgnW.mjs.map → device-flow-Bn-qiDrw.mjs.map} +1 -1
  332. package/dist/{email-console-BbU3RbWv.mjs → email-console-B9VS6eN4.mjs} +1 -1
  333. package/dist/{email-console-BbU3RbWv.mjs.map → email-console-B9VS6eN4.mjs.map} +1 -1
  334. package/dist/{error-CNn_w7jf.mjs → error-BQ1pxs0L.mjs} +2 -2
  335. package/dist/{error-CNn_w7jf.mjs.map → error-BQ1pxs0L.mjs.map} +1 -1
  336. package/dist/{escape-DPgcxcpL.mjs → escape-BtbJWyaW.mjs} +1 -1
  337. package/dist/{escape-DPgcxcpL.mjs.map → escape-BtbJWyaW.mjs.map} +1 -1
  338. package/dist/{fts-manager-Cx5z8jdA.mjs → fts-manager-DtiWqwNJ.mjs} +2 -2
  339. package/dist/{fts-manager-Cx5z8jdA.mjs.map → fts-manager-DtiWqwNJ.mjs.map} +1 -1
  340. package/dist/{hash-DlvIFn0b.mjs → hash-BowcuPwv.mjs} +1 -1
  341. package/dist/{hash-DlvIFn0b.mjs.map → hash-BowcuPwv.mjs.map} +1 -1
  342. package/dist/{import-KyxT1Mbs.mjs → import-DqSGOlmR.mjs} +4 -4
  343. package/dist/{import-KyxT1Mbs.mjs.map → import-DqSGOlmR.mjs.map} +1 -1
  344. package/dist/{index-D2VAiumu.d.mts → index-C2dLNT6S.d.mts} +172 -17
  345. package/dist/index-C2dLNT6S.d.mts.map +1 -0
  346. package/dist/{index-uT2yR66F.d.mts → index-ZDEwR7qW.d.mts} +3 -3
  347. package/dist/{index-uT2yR66F.d.mts.map → index-ZDEwR7qW.d.mts.map} +1 -1
  348. package/dist/index.d.mts +18 -17
  349. package/dist/index.mjs +63 -61
  350. package/dist/{init-lock-DlBHjf9-.mjs → init-lock-6b309ZrF.mjs} +2 -29
  351. package/dist/init-lock-6b309ZrF.mjs.map +1 -0
  352. package/dist/{load-Dq91b_DK.mjs → load-Dtoo7KcJ.mjs} +2 -2
  353. package/dist/{load-Dq91b_DK.mjs.map → load-Dtoo7KcJ.mjs.map} +1 -1
  354. package/dist/{loader-BqWjcH3h.mjs → loader-C8Z48Uof.mjs} +4 -4
  355. package/dist/{loader-BqWjcH3h.mjs.map → loader-C8Z48Uof.mjs.map} +1 -1
  356. package/dist/{manifest-schema-DFPeqMAn.mjs → manifest-schema-DDSbwkAL.mjs} +1 -1
  357. package/dist/{manifest-schema-DFPeqMAn.mjs.map → manifest-schema-DDSbwkAL.mjs.map} +1 -1
  358. package/dist/media/image-endpoint.d.mts +74 -0
  359. package/dist/media/image-endpoint.d.mts.map +1 -0
  360. package/dist/media/image-endpoint.mjs +162 -0
  361. package/dist/media/image-endpoint.mjs.map +1 -0
  362. package/dist/media/index.d.mts +1 -1
  363. package/dist/media/index.mjs +2 -2
  364. package/dist/media/local-runtime.d.mts +11 -11
  365. package/dist/media/local-runtime.mjs +9 -8
  366. package/dist/media/local-runtime.mjs.map +1 -1
  367. package/dist/{media-JOf3pNkw.mjs → media-Bw0sA6rb.mjs} +2 -2
  368. package/dist/{media-JOf3pNkw.mjs.map → media-Bw0sA6rb.mjs.map} +1 -1
  369. package/dist/{media-allowlist-_A0SuDn4.mjs → media-allowlist-DunzrKFM.mjs} +2 -2
  370. package/dist/{media-allowlist-_A0SuDn4.mjs.map → media-allowlist-DunzrKFM.mjs.map} +1 -1
  371. package/dist/{media-url-CqLd69IO.mjs → media-url-S22B6aPr.mjs} +1 -1
  372. package/dist/{media-url-CqLd69IO.mjs.map → media-url-S22B6aPr.mjs.map} +1 -1
  373. package/dist/{menus-DX4_E01q.mjs → menus-CYs0WZoL.mjs} +20 -6
  374. package/dist/menus-CYs0WZoL.mjs.map +1 -0
  375. package/dist/{menus-Ryk9L7fT.mjs → menus-DOs1MQJg.mjs} +120 -20
  376. package/dist/menus-DOs1MQJg.mjs.map +1 -0
  377. package/dist/{mime-YbtlEtvS.mjs → mime-DYpsOAny.mjs} +1 -1
  378. package/dist/{mime-YbtlEtvS.mjs.map → mime-DYpsOAny.mjs.map} +1 -1
  379. package/dist/{mode-CGXzIbD8.mjs → mode-CFh8Dw8G.mjs} +1 -1
  380. package/dist/{mode-CGXzIbD8.mjs.map → mode-CFh8Dw8G.mjs.map} +1 -1
  381. package/dist/{normalize-DKsg36ty.mjs → normalize-Uam4_Vkr.mjs} +1 -1
  382. package/dist/{normalize-DKsg36ty.mjs.map → normalize-Uam4_Vkr.mjs.map} +1 -1
  383. package/dist/{oauth-authorization-C2kVyjXI.mjs → oauth-authorization-BN-t83Uy.mjs} +5 -5
  384. package/dist/{oauth-authorization-C2kVyjXI.mjs.map → oauth-authorization-BN-t83Uy.mjs.map} +1 -1
  385. package/dist/{oauth-clients-BC873NCV.mjs → oauth-clients-R6I_V0qz.mjs} +1 -1
  386. package/dist/{oauth-clients-BC873NCV.mjs.map → oauth-clients-R6I_V0qz.mjs.map} +1 -1
  387. package/dist/{oauth-state-store-Cd--TUaq.mjs → oauth-state-store-Csoc6kkR.mjs} +1 -1
  388. package/dist/{oauth-state-store-Cd--TUaq.mjs.map → oauth-state-store-Csoc6kkR.mjs.map} +1 -1
  389. package/dist/{oauth-user-lookup-e4wOvDud.mjs → oauth-user-lookup-d7VYjTpx.mjs} +1 -1
  390. package/dist/{oauth-user-lookup-e4wOvDud.mjs.map → oauth-user-lookup-d7VYjTpx.mjs.map} +1 -1
  391. package/dist/object-cache/memory.d.mts +15 -0
  392. package/dist/object-cache/memory.d.mts.map +1 -0
  393. package/dist/object-cache/memory.mjs +56 -0
  394. package/dist/object-cache/memory.mjs.map +1 -0
  395. package/dist/object-cache-SEb2IM4Z.mjs +373 -0
  396. package/dist/object-cache-SEb2IM4Z.mjs.map +1 -0
  397. package/dist/{options-9kLgkE8m.d.mts → options-DFFpvNJU.d.mts} +3 -3
  398. package/dist/{options-9kLgkE8m.d.mts.map → options-DFFpvNJU.d.mts.map} +1 -1
  399. package/dist/{options-BPCVnesz.mjs → options-DTTML-Tx.mjs} +1 -1
  400. package/dist/{options-BPCVnesz.mjs.map → options-DTTML-Tx.mjs.map} +1 -1
  401. package/dist/page/index.d.mts +2 -2
  402. package/dist/{parse-DzSrk1t8.mjs → parse-qrHlnzTy.mjs} +2 -2
  403. package/dist/{parse-DzSrk1t8.mjs.map → parse-qrHlnzTy.mjs.map} +1 -1
  404. package/dist/{passkey-config-BpjbE_Uv.mjs → passkey-config-BJTbcnI5.mjs} +1 -1
  405. package/dist/{passkey-config-BpjbE_Uv.mjs.map → passkey-config-BJTbcnI5.mjs.map} +1 -1
  406. package/dist/{patterns-p-RBdTbM.mjs → patterns-BKmjvM7K.mjs} +1 -1
  407. package/dist/{patterns-p-RBdTbM.mjs.map → patterns-BKmjvM7K.mjs.map} +1 -1
  408. package/dist/{placeholder-2xumZh4g.mjs → placeholder-CIDCpyEY.mjs} +1 -1
  409. package/dist/{placeholder-2xumZh4g.mjs.map → placeholder-CIDCpyEY.mjs.map} +1 -1
  410. package/dist/{placeholder-BevVKfay.d.mts → placeholder-CirPauNG.d.mts} +1 -1
  411. package/dist/{placeholder-BevVKfay.d.mts.map → placeholder-CirPauNG.d.mts.map} +1 -1
  412. package/dist/plugin-types.d.mts +1 -1
  413. package/dist/plugin-utils.d.mts +9 -9
  414. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  415. package/dist/plugins/adapt-sandbox-entry.mjs +4 -2
  416. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  417. package/dist/{transport-1cIrOb1Y.mjs → portable-text-BIFhrU6T.mjs} +2 -135
  418. package/dist/portable-text-BIFhrU6T.mjs.map +1 -0
  419. package/dist/{preview-Dqv2hwXr.mjs → preview-IDye9SPQ.mjs} +2 -2
  420. package/dist/{preview-Dqv2hwXr.mjs.map → preview-IDye9SPQ.mjs.map} +1 -1
  421. package/dist/{public-url-D_zARuvZ.mjs → public-url-8lFyQ8ZF.mjs} +1 -1
  422. package/dist/{public-url-D_zARuvZ.mjs.map → public-url-8lFyQ8ZF.mjs.map} +1 -1
  423. package/dist/{query-Crm038Mc.mjs → query-B1RVFUNM.mjs} +159 -31
  424. package/dist/query-B1RVFUNM.mjs.map +1 -0
  425. package/dist/{rate-limit-hRTBqmw1.mjs → rate-limit-DfNAqIWW.mjs} +2 -2
  426. package/dist/{rate-limit-hRTBqmw1.mjs.map → rate-limit-DfNAqIWW.mjs.map} +1 -1
  427. package/dist/{redirect-C-OOkyku.mjs → redirect-B4wuX2A5.mjs} +1 -1
  428. package/dist/{redirect-C-OOkyku.mjs.map → redirect-B4wuX2A5.mjs.map} +1 -1
  429. package/dist/{redirect-CRWIt8Zj.mjs → redirect-Bifv_yHv.mjs} +3 -3
  430. package/dist/{redirect-CRWIt8Zj.mjs.map → redirect-Bifv_yHv.mjs.map} +1 -1
  431. package/dist/{redirects-CP3TnTLO.mjs → redirects-CT_vb3NV.mjs} +6 -6
  432. package/dist/{redirects-CP3TnTLO.mjs.map → redirects-CT_vb3NV.mjs.map} +1 -1
  433. package/dist/{redirects-6Zg2SoYo.mjs → redirects-DUvXgzCX.mjs} +8 -3
  434. package/dist/redirects-DUvXgzCX.mjs.map +1 -0
  435. package/dist/{registry-diMzD1Wf.mjs → registry-CjK96fHD.mjs} +6 -6
  436. package/dist/{registry-diMzD1Wf.mjs.map → registry-CjK96fHD.mjs.map} +1 -1
  437. package/dist/{request-cache-UwmBAiUK.mjs → request-cache-KCNHp_RJ.mjs} +1 -1
  438. package/dist/{request-cache-UwmBAiUK.mjs.map → request-cache-KCNHp_RJ.mjs.map} +1 -1
  439. package/dist/{request-meta-DPechd0W.mjs → request-meta-ei3ATilC.mjs} +2 -2
  440. package/dist/{request-meta-DPechd0W.mjs.map → request-meta-ei3ATilC.mjs.map} +1 -1
  441. package/dist/{resolve-B3NUUtVY.mjs → resolve-CpbBglV0.mjs} +1 -1
  442. package/dist/{resolve-B3NUUtVY.mjs.map → resolve-CpbBglV0.mjs.map} +1 -1
  443. package/dist/{runner--4wMWwKM.mjs → runner-DYYnW530.mjs} +201 -170
  444. package/dist/runner-DYYnW530.mjs.map +1 -0
  445. package/dist/{runner-C8vcbvCe.d.mts → runner-jdXtFEd3.d.mts} +2 -2
  446. package/dist/{runner-C8vcbvCe.d.mts.map → runner-jdXtFEd3.d.mts.map} +1 -1
  447. package/dist/runtime.d.mts +10 -10
  448. package/dist/runtime.mjs +3 -3
  449. package/dist/{schema-BDOkd3OU.mjs → schema-5z2Jfnbm.mjs} +13 -8
  450. package/dist/schema-5z2Jfnbm.mjs.map +1 -0
  451. package/dist/{search-Bs_J_EW-.mjs → search-D9FkoM-k.mjs} +4 -4
  452. package/dist/{search-Bs_J_EW-.mjs.map → search-D9FkoM-k.mjs.map} +1 -1
  453. package/dist/{secrets-C8xmE6mR.mjs → secrets-BUE5UQys.mjs} +5 -5
  454. package/dist/{secrets-C8xmE6mR.mjs.map → secrets-BUE5UQys.mjs.map} +1 -1
  455. package/dist/{sections-P0zuBlyz.mjs → sections-DWYZo6HK.mjs} +3 -3
  456. package/dist/{sections-P0zuBlyz.mjs.map → sections-DWYZo6HK.mjs.map} +1 -1
  457. package/dist/seed/index.d.mts +2 -2
  458. package/dist/seed/index.mjs +22 -21
  459. package/dist/seo/index.d.mts +1 -1
  460. package/dist/seo/index.mjs +1 -1
  461. package/dist/{seo-DpNgGQjF.mjs → seo-Cu47j3mJ.mjs} +5 -2
  462. package/dist/seo-Cu47j3mJ.mjs.map +1 -0
  463. package/dist/{seo-CLhm-Fmb.mjs → seo-DRnqnstF.mjs} +1 -1
  464. package/dist/{seo-CLhm-Fmb.mjs.map → seo-DRnqnstF.mjs.map} +1 -1
  465. package/dist/{service-CDQQnT8W.mjs → service-DTmtqCnw.mjs} +3 -3
  466. package/dist/{service-CDQQnT8W.mjs.map → service-DTmtqCnw.mjs.map} +1 -1
  467. package/dist/session-user-DmtPMNa1.mjs +51 -0
  468. package/dist/session-user-DmtPMNa1.mjs.map +1 -0
  469. package/dist/{settings-sO0Fif4p.mjs → settings-DUrVDN0c.mjs} +3 -3
  470. package/dist/{settings-sO0Fif4p.mjs.map → settings-DUrVDN0c.mjs.map} +1 -1
  471. package/dist/{settings-BjBsmVAo.mjs → settings-yaxu9mU-.mjs} +18 -10
  472. package/dist/settings-yaxu9mU-.mjs.map +1 -0
  473. package/dist/{setup-complete-CMMr-oZU.mjs → setup-complete-BWFHzgvZ.mjs} +2 -2
  474. package/dist/{setup-complete-CMMr-oZU.mjs.map → setup-complete-BWFHzgvZ.mjs.map} +1 -1
  475. package/dist/{setup-nonce-169xl4fV.mjs → setup-nonce-DFWG-J5c.mjs} +1 -1
  476. package/dist/{setup-nonce-169xl4fV.mjs.map → setup-nonce-DFWG-J5c.mjs.map} +1 -1
  477. package/dist/{single-flight-cache-C0UV1Npg.mjs → single-flight-cache-B5p5cNOL.mjs} +2 -2
  478. package/dist/{single-flight-cache-C0UV1Npg.mjs.map → single-flight-cache-B5p5cNOL.mjs.map} +1 -1
  479. package/dist/{site-url-vtsuOvSD.mjs → site-url-BOTPgmeM.mjs} +2 -2
  480. package/dist/{site-url-vtsuOvSD.mjs.map → site-url-BOTPgmeM.mjs.map} +1 -1
  481. package/dist/{slugify-Cjh1ssOZ.mjs → slugify-Cce3dTdg.mjs} +1 -1
  482. package/dist/{slugify-Cjh1ssOZ.mjs.map → slugify-Cce3dTdg.mjs.map} +1 -1
  483. package/dist/{ssrf-XO05Voq6.mjs → ssrf-B0VeRLrp.mjs} +1 -1
  484. package/dist/{ssrf-XO05Voq6.mjs.map → ssrf-B0VeRLrp.mjs.map} +1 -1
  485. package/dist/{status-2gZklYuj.mjs → status-CB2basWJ.mjs} +1 -1
  486. package/dist/{status-2gZklYuj.mjs.map → status-CB2basWJ.mjs.map} +1 -1
  487. package/dist/storage/local.d.mts +1 -1
  488. package/dist/storage/local.mjs +1 -1
  489. package/dist/storage/s3.d.mts +1 -1
  490. package/dist/storage/s3.mjs +1 -1
  491. package/dist/{taxonomies-BBxYA38v.mjs → taxonomies-Be9B1jEj.mjs} +156 -122
  492. package/dist/taxonomies-Be9B1jEj.mjs.map +1 -0
  493. package/dist/{taxonomies-DuESHWKI.mjs → taxonomies-CJmkEpdS.mjs} +7 -7
  494. package/dist/{taxonomies-DuESHWKI.mjs.map → taxonomies-CJmkEpdS.mjs.map} +1 -1
  495. package/dist/{taxonomy-CdllE4oq.mjs → taxonomy-B2HQuVf5.mjs} +19 -6
  496. package/dist/taxonomy-B2HQuVf5.mjs.map +1 -0
  497. package/dist/{tokens-DMkVjxrx.mjs → tokens-BvrJvIB6.mjs} +2 -2
  498. package/dist/{tokens-DMkVjxrx.mjs.map → tokens-BvrJvIB6.mjs.map} +1 -1
  499. package/dist/{transaction-x2tJQ-A1.mjs → transaction-qfqpPVpu.mjs} +1 -1
  500. package/dist/{transaction-x2tJQ-A1.mjs.map → transaction-qfqpPVpu.mjs.map} +1 -1
  501. package/dist/transport-B4rXnSTf.mjs +135 -0
  502. package/dist/transport-B4rXnSTf.mjs.map +1 -0
  503. package/dist/{transport-jdvsZEIt.d.mts → transport-k7YjfmEn.d.mts} +1 -1
  504. package/dist/{transport-jdvsZEIt.d.mts.map → transport-k7YjfmEn.d.mts.map} +1 -1
  505. package/dist/{trusted-proxy-CHp41Fjj.mjs → trusted-proxy-DY1_UqHr.mjs} +1 -1
  506. package/dist/{trusted-proxy-CHp41Fjj.mjs.map → trusted-proxy-DY1_UqHr.mjs.map} +1 -1
  507. package/dist/{types-DO7whVYU.d.mts → types-CIKX2HO5.d.mts} +2 -2
  508. package/dist/{types-DO7whVYU.d.mts.map → types-CIKX2HO5.d.mts.map} +1 -1
  509. package/dist/{types-u_XxjbS8.d.mts → types-CKNXt63n.d.mts} +1 -1
  510. package/dist/{types-u_XxjbS8.d.mts.map → types-CKNXt63n.d.mts.map} +1 -1
  511. package/dist/{types-BTnnBYVX.d.mts → types-CWGYDYqF.d.mts} +2 -2
  512. package/dist/{types-BTnnBYVX.d.mts.map → types-CWGYDYqF.d.mts.map} +1 -1
  513. package/dist/{types-CkEuk-Zr.d.mts → types-CgvhU-hF.d.mts} +1 -1
  514. package/dist/{types-CkEuk-Zr.d.mts.map → types-CgvhU-hF.d.mts.map} +1 -1
  515. package/dist/{types-BFgYtuKd.d.mts → types-CmivgZbp.d.mts} +1 -1
  516. package/dist/{types-BFgYtuKd.d.mts.map → types-CmivgZbp.d.mts.map} +1 -1
  517. package/dist/{types-Del0VMij.d.mts → types-Czg1S3gB.d.mts} +9 -1
  518. package/dist/{types-Del0VMij.d.mts.map → types-Czg1S3gB.d.mts.map} +1 -1
  519. package/dist/types-DCNNfeCv.d.mts +124 -0
  520. package/dist/types-DCNNfeCv.d.mts.map +1 -0
  521. package/dist/{types-Bzfk2yC8.d.mts → types-DPCpkzvS.d.mts} +1 -1
  522. package/dist/{types-Bzfk2yC8.d.mts.map → types-DPCpkzvS.d.mts.map} +1 -1
  523. package/dist/{types-DdkL6fyv.d.mts → types-DXLVzV2w.d.mts} +1 -1
  524. package/dist/{types-DdkL6fyv.d.mts.map → types-DXLVzV2w.d.mts.map} +1 -1
  525. package/dist/{types-BXSUSAjt.mjs → types-DXOIbece.mjs} +3 -3
  526. package/dist/{types-BXSUSAjt.mjs.map → types-DXOIbece.mjs.map} +1 -1
  527. package/dist/{types-DejCHqWT.mjs → types-DYF2_Vf4.mjs} +1 -1
  528. package/dist/{types-DejCHqWT.mjs.map → types-DYF2_Vf4.mjs.map} +1 -1
  529. package/dist/{types-BIduXPJk.mjs → types-tM44hEcf.mjs} +1 -1
  530. package/dist/{types-BIduXPJk.mjs.map → types-tM44hEcf.mjs.map} +1 -1
  531. package/dist/{user-C0um7wrg.mjs → user-Db-Rti_z.mjs} +3 -3
  532. package/dist/{user-C0um7wrg.mjs.map → user-Db-Rti_z.mjs.map} +1 -1
  533. package/dist/{utils-C4M981Br.mjs → utils-LbHcSRTj.mjs} +2 -2
  534. package/dist/{utils-C4M981Br.mjs.map → utils-LbHcSRTj.mjs.map} +1 -1
  535. package/dist/{validate-DGhQPXzI.mjs → validate-Bwl64sxv.mjs} +3 -3
  536. package/dist/{validate-DGhQPXzI.mjs.map → validate-Bwl64sxv.mjs.map} +1 -1
  537. package/dist/{validate-cJOiOvT2.d.mts → validate-CE2M0nvx.d.mts} +5 -5
  538. package/dist/{validate-cJOiOvT2.d.mts.map → validate-CE2M0nvx.d.mts.map} +1 -1
  539. package/dist/{validation-DVHjPM1M.mjs → validation-C_yOvFaO.mjs} +6 -6
  540. package/dist/{validation-DVHjPM1M.mjs.map → validation-C_yOvFaO.mjs.map} +1 -1
  541. package/dist/version-RnYXYjzD.mjs +7 -0
  542. package/dist/{version-BOjj_cfz.mjs.map → version-RnYXYjzD.mjs.map} +1 -1
  543. package/dist/{widgets-Ci6hLwfO.mjs → widgets-8GONuRHo.mjs} +4 -4
  544. package/dist/{widgets-Ci6hLwfO.mjs.map → widgets-8GONuRHo.mjs.map} +1 -1
  545. package/dist/{zod-generator-CarzgPAu.mjs → zod-generator-D481JjzD.mjs} +3 -3
  546. package/dist/{zod-generator-CarzgPAu.mjs.map → zod-generator-D481JjzD.mjs.map} +1 -1
  547. package/package.json +18 -6
  548. package/src/api/handlers/api-tokens.ts +19 -0
  549. package/src/api/handlers/comment-reactions.ts +165 -0
  550. package/src/api/handlers/schema.ts +8 -0
  551. package/src/api/schemas/comments.ts +10 -0
  552. package/src/astro/image-endpoint.ts +84 -0
  553. package/src/astro/index.ts +6 -0
  554. package/src/astro/integration/index.ts +83 -22
  555. package/src/astro/integration/routes.ts +5 -0
  556. package/src/astro/integration/runtime.ts +65 -1
  557. package/src/astro/integration/virtual-modules.ts +32 -0
  558. package/src/astro/integration/vite-config.ts +16 -0
  559. package/src/astro/middleware/auth.ts +4 -3
  560. package/src/astro/middleware/stream-end-metrics.ts +11 -1
  561. package/src/astro/middleware.ts +41 -3
  562. package/src/astro/object-cache/adapters.ts +50 -0
  563. package/src/astro/prefetch.ts +77 -0
  564. package/src/astro/routes/api/comments/[collection]/[contentId]/reactions.ts +97 -0
  565. package/src/astro/routes/api/setup/dev-bypass.ts +5 -1
  566. package/src/astro/routes/api/snapshot.ts +3 -1
  567. package/src/astro/session-user.ts +57 -0
  568. package/src/astro/types.ts +1 -0
  569. package/src/comments/query.ts +78 -7
  570. package/src/comments/ranking.ts +58 -0
  571. package/src/components/Comments.astro +136 -1
  572. package/src/database/instrumentation.ts +21 -1
  573. package/src/database/migrations/044_comment_reactions.ts +56 -0
  574. package/src/database/migrations/runner.ts +2 -0
  575. package/src/database/repositories/byline.ts +13 -0
  576. package/src/database/repositories/comment-reaction.ts +132 -0
  577. package/src/database/repositories/comment.ts +10 -0
  578. package/src/database/repositories/content.ts +25 -3
  579. package/src/database/repositories/menu.ts +13 -1
  580. package/src/database/repositories/seo.ts +3 -0
  581. package/src/database/repositories/taxonomy.ts +13 -1
  582. package/src/database/types.ts +9 -0
  583. package/src/emdash-runtime.ts +18 -4
  584. package/src/index.ts +22 -0
  585. package/src/mcp/server.ts +307 -20
  586. package/src/media/image-endpoint.ts +167 -0
  587. package/src/menus/index.ts +11 -4
  588. package/src/object-cache/codec.ts +73 -0
  589. package/src/object-cache/index.ts +495 -0
  590. package/src/object-cache/memory.ts +91 -0
  591. package/src/object-cache/types.ts +124 -0
  592. package/src/plugins/adapt-sandbox-entry.ts +11 -1
  593. package/src/query.ts +196 -17
  594. package/src/schema/query.ts +11 -4
  595. package/src/settings/index.ts +18 -5
  596. package/src/taxonomies/index.ts +237 -165
  597. package/src/virtual-modules.d.ts +11 -0
  598. package/dist/api-DxjIV2o8.mjs.map +0 -1
  599. package/dist/api-tokens-BFFkB0jB.mjs.map +0 -1
  600. package/dist/byline-V_Qp1Ziw.mjs.map +0 -1
  601. package/dist/byline-fields-CQJRIQkn.d.mts.map +0 -1
  602. package/dist/comment-sqQxNpN3.mjs.map +0 -1
  603. package/dist/content-BIlVx-RX.mjs.map +0 -1
  604. package/dist/index-D2VAiumu.d.mts.map +0 -1
  605. package/dist/init-lock-DlBHjf9-.mjs.map +0 -1
  606. package/dist/menus-DX4_E01q.mjs.map +0 -1
  607. package/dist/menus-Ryk9L7fT.mjs.map +0 -1
  608. package/dist/query-Crm038Mc.mjs.map +0 -1
  609. package/dist/redirects-6Zg2SoYo.mjs.map +0 -1
  610. package/dist/runner--4wMWwKM.mjs.map +0 -1
  611. package/dist/schema-BDOkd3OU.mjs.map +0 -1
  612. package/dist/seo-DpNgGQjF.mjs.map +0 -1
  613. package/dist/settings-BjBsmVAo.mjs.map +0 -1
  614. package/dist/taxonomies-BBxYA38v.mjs.map +0 -1
  615. package/dist/taxonomy-CdllE4oq.mjs.map +0 -1
  616. package/dist/transport-1cIrOb1Y.mjs.map +0 -1
  617. package/dist/version-BOjj_cfz.mjs +0 -7
  618. /package/dist/{api-tokens-C7ywRx7l.mjs → api-tokens-BVH-H8Z9.mjs} +0 -0
  619. /package/dist/{ssrf-CRZGzjdL.mjs → ssrf-Cz1e2QPn.mjs} +0 -0
  620. /package/dist/{types-BoRm8-pp.mjs → types-Bfr2Pj6z.mjs} +0 -0
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Public comment reaction endpoints (Tier 1 of the best-in-class comments RFC).
3
+ *
4
+ * GET /_emdash/api/comments/:collection/:contentId/reactions
5
+ * - Aggregate reaction counts for the content's approved comments, plus the
6
+ * current visitor's active reactions.
7
+ * POST /_emdash/api/comments/:collection/:contentId/reactions
8
+ * - Toggle a reaction on a comment. Public, honeypot- and rate-limit-gated.
9
+ *
10
+ * Inherits the `/_emdash/api/comments/` public prefix (no auth); the POST still
11
+ * requires the `X-EmDash-Request: 1` CSRF header like the comment POST.
12
+ */
13
+
14
+ import type { APIRoute } from "astro";
15
+
16
+ import { apiError, apiSuccess, handleError, requireDb, unwrapResult } from "#api/error.js";
17
+ import { handleReactionCounts, handleReactionToggle } from "#api/handlers/comment-reactions.js";
18
+ import { hashIp } from "#api/handlers/comments.js";
19
+ import { isParseError, parseBody } from "#api/parse.js";
20
+ import { createReactionBody } from "#api/schemas.js";
21
+ import { resolveSecretsCached } from "#config/secrets.js";
22
+ import { extractRequestMeta } from "#plugins/request-meta.js";
23
+
24
+ export const prerender = false;
25
+
26
+ export const GET: APIRoute = async ({ params, request, locals }) => {
27
+ const { emdash } = locals;
28
+ const { collection, contentId } = params;
29
+
30
+ if (!collection || !contentId) {
31
+ return apiError("VALIDATION_ERROR", "Collection and content ID required", 400);
32
+ }
33
+
34
+ const dbErr = requireDb(emdash?.db);
35
+ if (dbErr) return dbErr;
36
+
37
+ try {
38
+ // Salted voter hash from request IP (same primitive as comment ip_hash).
39
+ // Behind Cloudflare (CF-Connecting-IP) or a configured trusted proxy this
40
+ // is per-visitor. Without a trusted IP it collapses to a shared "unknown"
41
+ // bucket, so reaction dedup degrades for those visitors — a real
42
+ // per-visitor token is Tier 2 (visitor identity). Operators should set
43
+ // trustedProxyHeaders; see the comment ingest route for the same note.
44
+ const meta = extractRequestMeta(request, emdash.config);
45
+ let voterHash = "unknown";
46
+ if (meta.ip) {
47
+ const { ipSalt } = await resolveSecretsCached(emdash.db);
48
+ voterHash = await hashIp(meta.ip, ipSalt);
49
+ }
50
+
51
+ const result = await handleReactionCounts(emdash.db, collection, contentId, voterHash);
52
+ return unwrapResult(result);
53
+ } catch (error) {
54
+ return handleError(error, "Failed to read reactions", "REACTION_COUNTS_ERROR");
55
+ }
56
+ };
57
+
58
+ export const POST: APIRoute = async ({ params, request, locals }) => {
59
+ const { emdash } = locals;
60
+ const { collection, contentId } = params;
61
+
62
+ if (!collection || !contentId) {
63
+ return apiError("VALIDATION_ERROR", "Collection and content ID required", 400);
64
+ }
65
+
66
+ const dbErr = requireDb(emdash?.db);
67
+ if (dbErr) return dbErr;
68
+
69
+ try {
70
+ const body = await parseBody(request, createReactionBody);
71
+ if (isParseError(body)) return body;
72
+
73
+ // Anti-spam: honeypot — hidden field filled only by bots. Silently accept.
74
+ if (body.website_url) {
75
+ return apiSuccess({ reacted: false, counts: {} });
76
+ }
77
+
78
+ const meta = extractRequestMeta(request, emdash.config);
79
+ let voterHash = "unknown";
80
+ if (meta.ip) {
81
+ const { ipSalt } = await resolveSecretsCached(emdash.db);
82
+ voterHash = await hashIp(meta.ip, ipSalt);
83
+ }
84
+
85
+ const result = await handleReactionToggle(emdash.db, {
86
+ collection,
87
+ contentId,
88
+ commentId: body.commentId,
89
+ reaction: body.reaction,
90
+ voterHash,
91
+ });
92
+
93
+ return unwrapResult(result, 200);
94
+ } catch (error) {
95
+ return handleError(error, "Failed to toggle reaction", "REACTION_TOGGLE_ERROR");
96
+ }
97
+ };
@@ -23,7 +23,7 @@ import { ulid } from "ulidx";
23
23
 
24
24
  import { apiError, apiSuccess, handleError } from "#api/error.js";
25
25
  import { escapeHtml } from "#api/escape.js";
26
- import { handleApiTokenCreate } from "#api/handlers/api-tokens.js";
26
+ import { deleteApiTokensByName, handleApiTokenCreate } from "#api/handlers/api-tokens.js";
27
27
  import { getPublicOrigin } from "#api/public-url.js";
28
28
  import { isSafeRedirect } from "#api/redirect.js";
29
29
  import { runMigrations } from "#db/migrations/runner.js";
@@ -134,6 +134,10 @@ async function handleDevBypass(context: Parameters<APIRoute>[0]): Promise<Respon
134
134
  // Optionally create a PAT token (?token=1) for headless/CLI testing.
135
135
  let token: string | undefined;
136
136
  if (url.searchParams.has("token")) {
137
+ // Idempotent by name: a prior reset can leave a stale dev-bypass-token,
138
+ // and the raw token is only available at creation, so drop any existing
139
+ // one and mint a fresh, usable PAT rather than accumulating duplicates.
140
+ await deleteApiTokensByName(emdash.db, user.id, "dev-bypass-token");
137
141
  const result = await handleApiTokenCreate(emdash.db, user.id, {
138
142
  name: "dev-bypass-token",
139
143
  scopes: [
@@ -20,6 +20,8 @@ import {
20
20
  import { getPublicOrigin } from "#api/public-url.js";
21
21
  import { resolveSecretsCached } from "#config/secrets.js";
22
22
 
23
+ import { resolveSessionUser } from "../../session-user.js";
24
+
23
25
  export const prerender = false;
24
26
 
25
27
  export const GET: APIRoute = async ({ request, locals, url, session }) => {
@@ -31,7 +33,7 @@ export const GET: APIRoute = async ({ request, locals, url, session }) => {
31
33
  if (!user && session && emdash?.db) {
32
34
  try {
33
35
  const { createKyselyAdapter } = await import("@emdash-cms/auth/adapters/kysely");
34
- const sessionUser = await session.get("user");
36
+ const sessionUser = await resolveSessionUser(session);
35
37
  if (sessionUser?.id) {
36
38
  const adapter = createKyselyAdapter(emdash.db);
37
39
  const resolved = await adapter.getUserById(sessionUser.id);
@@ -0,0 +1,57 @@
1
+ import { after } from "../after.js";
2
+
3
+ /**
4
+ * Backstop timeout for resolving the session user. A live session-store read
5
+ * settles in a few ms, so this only ever fires on a genuinely stalled read.
6
+ */
7
+ export const SESSION_GET_TIMEOUT_MS = 3_000;
8
+
9
+ /**
10
+ * Resolve the Astro session user without risking an isolate-wide hang.
11
+ *
12
+ * On Cloudflare Workers, a request cancelled mid-`session.get()` (client
13
+ * disconnect, context teardown) can leave the underlying session-store read as
14
+ * a promise that never settles — neither resolving nor rejecting. Awaiting it
15
+ * directly hangs the request, and because the stalled promise is shared at the
16
+ * isolate level, every later session-bearing request hangs too (observed as
17
+ * 0-CPU, multi-minute, `canceled` responses; see #1274). A surrounding
18
+ * try/catch cannot help: the promise never rejects.
19
+ *
20
+ * Two layers, mirroring the reclaimable-cache pattern used elsewhere in core:
21
+ * 1. `after()` anchors the read so a cancelled request still drives it to
22
+ * completion — the promise settles and the isolate is not poisoned for
23
+ * subsequent requests (prevents the hang rather than merely surviving it).
24
+ * 2. A timeout is a fail-closed backstop: a still-stalled (or rejecting) read
25
+ * resolves to `undefined`, and every caller treats the absence of a session
26
+ * user as unauthenticated (anonymous on public routes, 401/redirect on
27
+ * protected ones). It can only ever drop privileges for that one request,
28
+ * never grant them.
29
+ *
30
+ * Used by every session read on the request path: the main middleware (the
31
+ * first read on a session-bearing request), the auth middleware, and the
32
+ * preview-snapshot route (which bypasses the auth middleware).
33
+ */
34
+ export async function resolveSessionUser<T>(
35
+ session: { get(key: "user"): Promise<T> } | undefined,
36
+ timeoutMs = SESSION_GET_TIMEOUT_MS,
37
+ ): Promise<T | undefined> {
38
+ if (!session) return undefined;
39
+ const read: Promise<T | undefined> = Promise.resolve(session.get("user")).catch(() => undefined);
40
+ // Keep the worker alive past response/cancellation so the read completes and
41
+ // the shared promise settles — this is what prevents the isolate poisoning.
42
+ after(() =>
43
+ read.then(
44
+ () => undefined,
45
+ () => undefined,
46
+ ),
47
+ );
48
+ let timer: ReturnType<typeof setTimeout> | undefined;
49
+ const timeout = new Promise<undefined>((resolve) => {
50
+ timer = setTimeout(resolve, timeoutMs, undefined);
51
+ });
52
+ try {
53
+ return await Promise.race([read, timeout]);
54
+ } finally {
55
+ clearTimeout(timer);
56
+ }
57
+ }
@@ -265,6 +265,7 @@ export interface EmDashHandlers {
265
265
  slug?: string;
266
266
  status?: string;
267
267
  authorId?: string;
268
+ bylines?: Array<{ bylineId: string; roleLabel?: string | null }>;
268
269
  locale?: string;
269
270
  translationOf?: string;
270
271
  createdAt?: string | null;
@@ -7,15 +7,26 @@
7
7
 
8
8
  import type { Kysely } from "kysely";
9
9
 
10
+ import { CommentReactionRepository } from "../database/repositories/comment-reaction.js";
10
11
  import { CommentRepository } from "../database/repositories/comment.js";
11
12
  import type { PublicComment } from "../database/repositories/comment.js";
12
13
  import type { Database } from "../database/types.js";
13
14
  import { getDb } from "../loader.js";
15
+ import { cachedQuery, CacheNamespace } from "../object-cache/index.js";
16
+ import { reactionScore } from "./ranking.js";
14
17
 
15
18
  export interface GetCommentsOptions {
16
19
  collection: string;
17
20
  contentId: string;
18
21
  threaded?: boolean;
22
+ /** Attach aggregate reaction counts to each comment. */
23
+ reactions?: boolean;
24
+ /**
25
+ * Order of top-level comments. `oldest` (default) preserves the existing
26
+ * chronological display; `best` ranks by Wilson-scored reactions (implies
27
+ * `reactions`). Replies always stay chronological.
28
+ */
29
+ sort?: "oldest" | "best";
19
30
  }
20
31
 
21
32
  export interface GetCommentsResult {
@@ -38,8 +49,21 @@ export interface GetCommentsResult {
38
49
  * ```
39
50
  */
40
51
  export async function getComments(options: GetCommentsOptions): Promise<GetCommentsResult> {
41
- const db = await getDb();
42
- return getCommentsWithDb(db, options);
52
+ // The result varies by every option getCommentsWithDb branches on, so all of
53
+ // them belong in the key. `best` implies reactions, so normalize the reaction
54
+ // flag — `{ sort: "best" }` and `{ sort: "best", reactions: true }` produce
55
+ // identical output and should share one entry.
56
+ const sort = options.sort ?? "oldest";
57
+ const withReactions = options.reactions || sort === "best";
58
+ const threaded = options.threaded ? "t" : "f";
59
+ return cachedQuery({
60
+ namespace: CacheNamespace.COMMENTS,
61
+ key: `comments:${options.collection}:${options.contentId}:${threaded}:${withReactions ? "r" : "n"}:${sort}`,
62
+ load: async () => {
63
+ const db = await getDb();
64
+ return getCommentsWithDb(db, options);
65
+ },
66
+ });
43
67
  }
44
68
 
45
69
  /**
@@ -65,16 +89,63 @@ export async function getCommentsWithDb(
65
89
  limit: MAX_COMMENTS,
66
90
  });
67
91
 
68
- if (options.threaded) {
69
- const threaded = CommentRepository.assembleThreads(result.items);
70
- const items = threaded.map((c) => CommentRepository.toPublicComment(c));
71
- return { items, total };
92
+ const items: PublicComment[] = options.threaded
93
+ ? CommentRepository.assembleThreads(result.items).map((c) =>
94
+ CommentRepository.toPublicComment(c),
95
+ )
96
+ : result.items.map((c) => CommentRepository.toPublicComment(c));
97
+
98
+ // `best` sort needs reaction data, so it implies reactions.
99
+ if (options.reactions || options.sort === "best") {
100
+ await attachReactions(db, items);
101
+ if (options.sort === "best") {
102
+ sortByBest(items);
103
+ }
72
104
  }
73
105
 
74
- const items = result.items.map((c) => CommentRepository.toPublicComment(c));
75
106
  return { items, total };
76
107
  }
77
108
 
109
+ /**
110
+ * Attach aggregate reaction counts to a list of public comments (and their
111
+ * replies), in a single batched query.
112
+ */
113
+ async function attachReactions(db: Kysely<Database>, items: PublicComment[]): Promise<void> {
114
+ const ids: string[] = [];
115
+ for (const comment of items) {
116
+ ids.push(comment.id);
117
+ if (comment.replies) {
118
+ for (const reply of comment.replies) ids.push(reply.id);
119
+ }
120
+ }
121
+ if (ids.length === 0) return;
122
+
123
+ const counts = await new CommentReactionRepository(db).countsForComments(ids);
124
+
125
+ const assign = (comment: PublicComment) => {
126
+ const reactions = counts.get(comment.id);
127
+ if (reactions) comment.reactions = reactions;
128
+ };
129
+ for (const comment of items) {
130
+ assign(comment);
131
+ comment.replies?.forEach(assign);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Sort top-level comments by Wilson-scored reactions (descending), tie-broken
137
+ * by oldest-first to keep ordering stable.
138
+ */
139
+ function sortByBest(items: PublicComment[]): void {
140
+ items.sort((a, b) => {
141
+ const scoreDelta = reactionScore(b.reactions ?? {}) - reactionScore(a.reactions ?? {});
142
+ if (scoreDelta !== 0) return scoreDelta;
143
+ if (a.createdAt < b.createdAt) return -1;
144
+ if (a.createdAt > b.createdAt) return 1;
145
+ return 0;
146
+ });
147
+ }
148
+
78
149
  /**
79
150
  * Get the count of approved comments for a content item.
80
151
  *
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Comment ranking utilities (Tier 1 of the best-in-class comments RFC).
3
+ *
4
+ * Wilson score lower-bound (95% confidence) — the same primitive Reddit uses
5
+ * for its "Best" comment sort. Ranks by the statistical lower bound of the
6
+ * positive-reaction proportion rather than the raw count, so a comment with a
7
+ * couple of reactions can't outrank a heavily-reacted one until it earns
8
+ * confidence, and a late-but-popular comment still rises (submission time is
9
+ * irrelevant).
10
+ *
11
+ * Positive-only reactions (the recommended default) degrade gracefully: with
12
+ * `down = 0` the score still increases monotonically with `up` while penalising
13
+ * low-sample comments — wilson(1,0) ≈ 0.21, wilson(10,0) ≈ 0.73,
14
+ * wilson(200,0) ≈ 0.98.
15
+ */
16
+
17
+ /** z for a 95% two-sided confidence interval. */
18
+ const DEFAULT_Z = 1.96;
19
+
20
+ /** Reactions that count against a comment when both signals are in use. */
21
+ const NEGATIVE_REACTIONS: ReadonlySet<string> = new Set(["dislike", "down"]);
22
+
23
+ /**
24
+ * Wilson score lower bound of the positive proportion.
25
+ *
26
+ * @param up count of positive reactions
27
+ * @param down count of negative reactions
28
+ * @returns a score in [0, 1]; 0 when there are no reactions
29
+ */
30
+ export function wilsonLowerBound(up: number, down: number, z: number = DEFAULT_Z): number {
31
+ const n = up + down;
32
+ if (n <= 0) return 0;
33
+ const phat = up / n;
34
+ const z2 = z * z;
35
+ const denom = 1 + z2 / n;
36
+ const centre = phat + z2 / (2 * n);
37
+ const margin = z * Math.sqrt((phat * (1 - phat) + z2 / (4 * n)) / n);
38
+ return (centre - margin) / denom;
39
+ }
40
+
41
+ /**
42
+ * Reduce a per-reaction count map to a single rank score via the Wilson
43
+ * lower bound. Any reaction not in {@link NEGATIVE_REACTIONS} is treated as
44
+ * positive, so the positive-only default (just `like`) works without special
45
+ * casing.
46
+ */
47
+ export function reactionScore(counts: Record<string, number>): number {
48
+ let up = 0;
49
+ let down = 0;
50
+ for (const [reaction, count] of Object.entries(counts)) {
51
+ if (NEGATIVE_REACTIONS.has(reaction)) {
52
+ down += count;
53
+ } else {
54
+ up += count;
55
+ }
56
+ }
57
+ return wilsonLowerBound(up, down);
58
+ }
@@ -21,6 +21,10 @@ interface Props {
21
21
  collection: string;
22
22
  contentId: string;
23
23
  threaded?: boolean;
24
+ /** Show a like/reaction button per comment (opt-in). */
25
+ reactions?: boolean;
26
+ /** Order of top-level comments: chronological (default) or Wilson-ranked. */
27
+ sort?: "oldest" | "best";
24
28
  class?: string;
25
29
  }
26
30
 
@@ -28,13 +32,15 @@ const {
28
32
  collection,
29
33
  contentId,
30
34
  threaded = false,
35
+ reactions = false,
36
+ sort = "oldest",
31
37
  class: className,
32
38
  } = Astro.props;
33
39
 
34
40
  const collectionInfo = await getCollectionInfo(collection);
35
41
  const enabled = collectionInfo?.commentsEnabled ?? false;
36
42
  const { items, total } = enabled
37
- ? await getComments({ collection, contentId, threaded })
43
+ ? await getComments({ collection, contentId, threaded, reactions, sort })
38
44
  : { items: [], total: 0 };
39
45
 
40
46
  const URL_RE = /https?:\/\/[^\s<>"')\]]+/g;
@@ -62,15 +68,72 @@ function escapeHtml(text: string): string {
62
68
  function formatBody(text: string): string {
63
69
  return autoLinkUrls(escapeHtml(text));
64
70
  }
71
+
72
+ // Progressive-enhancement script for reactions. Emitted verbatim (is:inline)
73
+ // only when `reactions` is enabled, so non-reaction pages ship zero extra JS.
74
+ // Avoids template literals internally so it survives the outer backticks.
75
+ const REACTION_SCRIPT = `
76
+ (() => {
77
+ const sections = document.querySelectorAll('[data-ec-comments][data-ec-reactions]');
78
+ sections.forEach((section) => {
79
+ const collection = section.getAttribute('data-collection');
80
+ const contentId = section.getAttribute('data-content-id');
81
+ if (!collection || !contentId) return;
82
+ const base = '/_emdash/api/comments/' + encodeURIComponent(collection) + '/' + encodeURIComponent(contentId) + '/reactions';
83
+ const headers = { 'X-EmDash-Request': '1' };
84
+
85
+ fetch(base, { headers: headers })
86
+ .then((r) => (r.ok ? r.json() : null))
87
+ .then((payload) => {
88
+ const viewer = payload && payload.data && payload.data.viewer;
89
+ if (!viewer) return;
90
+ Object.keys(viewer).forEach((commentId) => {
91
+ (viewer[commentId] || []).forEach((reaction) => {
92
+ const sel = '.ec-reaction[data-comment-id="' + commentId + '"][data-ec-reaction="' + reaction + '"]';
93
+ const btn = section.querySelector(sel);
94
+ if (btn) btn.setAttribute('aria-pressed', 'true');
95
+ });
96
+ });
97
+ })
98
+ .catch(() => {});
99
+
100
+ section.addEventListener('click', (event) => {
101
+ const btn = event.target.closest('.ec-reaction');
102
+ if (!btn || !section.contains(btn)) return;
103
+ const commentId = btn.getAttribute('data-comment-id');
104
+ const reaction = btn.getAttribute('data-ec-reaction');
105
+ if (!commentId || !reaction) return;
106
+ btn.disabled = true;
107
+ fetch(base, {
108
+ method: 'POST',
109
+ headers: Object.assign({ 'Content-Type': 'application/json' }, headers),
110
+ body: JSON.stringify({ commentId: commentId, reaction: reaction }),
111
+ })
112
+ .then((r) => (r.ok ? r.json() : null))
113
+ .then((payload) => {
114
+ const data = payload && payload.data;
115
+ if (!data) return;
116
+ btn.setAttribute('aria-pressed', data.reacted ? 'true' : 'false');
117
+ const countEl = btn.querySelector('[data-ec-reaction-count]');
118
+ if (countEl) countEl.textContent = String((data.counts && data.counts[reaction]) || 0);
119
+ })
120
+ .catch(() => {})
121
+ .finally(() => { btn.disabled = false; });
122
+ });
123
+ });
124
+ })();
125
+ `;
65
126
  ---
66
127
 
67
128
  {
68
129
  enabled && (
130
+ <Fragment>
69
131
  <section
70
132
  class:list={["ec-comments", className]}
71
133
  data-ec-comments
72
134
  data-collection={collection}
73
135
  data-content-id={contentId}
136
+ {...(reactions ? { "data-ec-reactions": "" } : {})}
74
137
  >
75
138
  <h3 class="ec-comments-heading">
76
139
  {total === 0
@@ -112,6 +175,25 @@ function formatBody(text: string): string {
112
175
  class="ec-comment-body"
113
176
  set:html={formatBody(comment.body)}
114
177
  />
178
+ {reactions && (
179
+ <div class="ec-comment-actions">
180
+ <button
181
+ type="button"
182
+ class="ec-reaction"
183
+ data-ec-reaction="like"
184
+ data-comment-id={comment.id}
185
+ aria-pressed="false"
186
+ >
187
+ <span class="ec-reaction-icon" aria-hidden="true">
188
+ &#x2661;
189
+ </span>
190
+ <span class="ec-reaction-label">Like</span>
191
+ <span class="ec-reaction-count" data-ec-reaction-count>
192
+ {comment.reactions?.like ?? 0}
193
+ </span>
194
+ </button>
195
+ </div>
196
+ )}
115
197
  {threaded && comment.replies && comment.replies.length > 0 && (
116
198
  <ol class="ec-comment-replies">
117
199
  {comment.replies.map((reply: PublicComment) => (
@@ -153,6 +235,21 @@ function formatBody(text: string): string {
153
235
  class="ec-comment-body"
154
236
  set:html={formatBody(reply.body)}
155
237
  />
238
+ {reactions && (
239
+ <div class="ec-comment-actions">
240
+ <button
241
+ type="button"
242
+ class="ec-reaction"
243
+ data-ec-reaction="like"
244
+ data-comment-id={reply.id}
245
+ aria-pressed="false"
246
+ >
247
+ <span class="ec-reaction-icon" aria-hidden="true">&#x2661;</span>
248
+ <span class="ec-reaction-label">Like</span>
249
+ <span class="ec-reaction-count" data-ec-reaction-count>{reply.reactions?.like ?? 0}</span>
250
+ </button>
251
+ </div>
252
+ )}
156
253
  </article>
157
254
  </li>
158
255
  ))}
@@ -164,6 +261,8 @@ function formatBody(text: string): string {
164
261
  </ol>
165
262
  )}
166
263
  </section>
264
+ {reactions && <script is:inline set:html={REACTION_SCRIPT} />}
265
+ </Fragment>
167
266
  )
168
267
  }
169
268
 
@@ -229,4 +328,40 @@ function formatBody(text: string): string {
229
328
  white-space: pre-wrap;
230
329
  word-break: break-word;
231
330
  }
331
+
332
+ .ec-comment-actions {
333
+ margin-top: 0.5rem;
334
+ }
335
+
336
+ .ec-reaction {
337
+ display: inline-flex;
338
+ align-items: center;
339
+ gap: 0.35rem;
340
+ font: inherit;
341
+ font-size: 0.875em;
342
+ color: inherit;
343
+ background: none;
344
+ border: var(--ec-comment-border);
345
+ border-radius: 999px;
346
+ padding: 0.15rem 0.6rem;
347
+ cursor: pointer;
348
+ }
349
+
350
+ .ec-reaction[aria-pressed="true"] {
351
+ border-color: currentColor;
352
+ font-weight: 600;
353
+ }
354
+
355
+ .ec-reaction[aria-pressed="true"] .ec-reaction-icon {
356
+ color: #e0245e;
357
+ }
358
+
359
+ .ec-reaction:disabled {
360
+ opacity: 0.5;
361
+ cursor: default;
362
+ }
363
+
364
+ .ec-reaction-count {
365
+ font-variant-numeric: tabular-nums;
366
+ }
232
367
  </style>
@@ -33,6 +33,19 @@ export interface QueryRecorder {
33
33
  route: string;
34
34
  method: string;
35
35
  phase: string;
36
+ /**
37
+ * Set once the recorder has been emitted, so a flush is idempotent.
38
+ * Without this, a fallback flush and the stream-end flush could both
39
+ * fire and double-emit the events.
40
+ */
41
+ flushed?: boolean;
42
+ /**
43
+ * Set when the response body is wrapped for stream-end metrics. The
44
+ * recorder is then flushed when the body finishes streaming (so it
45
+ * captures queries issued by components during streaming) rather than
46
+ * when middleware returns (headers ready, body not yet streamed).
47
+ */
48
+ deferredFlush?: boolean;
36
49
  }
37
50
 
38
51
  export function createRecorder(route: string, method: string, phase: string): QueryRecorder {
@@ -60,9 +73,16 @@ export function recordEvent(
60
73
  * harness pipes the child's stdout, filters lines beginning with
61
74
  * QUERY_LOG_PREFIX, and writes them to its own file. Using stdout means
62
75
  * the sink works uniformly in Node and in workerd (which has no fs).
76
+ *
77
+ * Idempotent: the first call emits and marks the recorder flushed, later
78
+ * calls no-op. For streamed responses the flush is deferred to stream end
79
+ * (see wrapBodyForStreamMetrics) so it captures queries issued while the
80
+ * body is still rendering; bodyless responses fall back to a flush when
81
+ * middleware returns.
63
82
  */
64
83
  export function flushRecorder(rec: QueryRecorder): void {
65
- if (rec.events.length === 0) return;
84
+ if (rec.flushed) return;
85
+ rec.flushed = true;
66
86
  for (const e of rec.events) {
67
87
  console.log(`${QUERY_LOG_PREFIX} ${JSON.stringify(e)}`);
68
88
  }
@@ -0,0 +1,56 @@
1
+ import type { Kysely } from "kysely";
2
+
3
+ import { currentTimestamp } from "../dialect-helpers.js";
4
+
5
+ /**
6
+ * Comment reactions (Tier 1 of the best-in-class comments RFC).
7
+ *
8
+ * One row per (comment, voter, reaction). Toggle semantics are enforced by the
9
+ * unique index — a voter either has a given reaction on a comment or doesn't.
10
+ * `voter_hash` is a salted SHA-256 of the visitor's IP (same primitive as
11
+ * `_emdash_comments.ip_hash`), so no cleartext identity is stored.
12
+ *
13
+ * Additive and backward-compatible: a new table, no change to existing schema.
14
+ */
15
+ export async function up(db: Kysely<unknown>): Promise<void> {
16
+ await db.schema
17
+ .createTable("_emdash_comment_reactions")
18
+ .ifNotExists()
19
+ .addColumn("id", "text", (col) => col.primaryKey())
20
+ .addColumn("comment_id", "text", (col) =>
21
+ col.notNull().references("_emdash_comments.id").onDelete("cascade"),
22
+ )
23
+ .addColumn("reaction", "text", (col) => col.notNull().defaultTo("like"))
24
+ .addColumn("voter_hash", "text", (col) => col.notNull())
25
+ .addColumn("created_at", "text", (col) => col.defaultTo(currentTimestamp(db)))
26
+ .execute();
27
+
28
+ // One reaction of a given type per voter per comment (toggle semantics).
29
+ await db.schema
30
+ .createIndex("idx_comment_reactions_unique")
31
+ .ifNotExists()
32
+ .on("_emdash_comment_reactions")
33
+ .columns(["comment_id", "voter_hash", "reaction"])
34
+ .unique()
35
+ .execute();
36
+
37
+ // Aggregate counts per comment (GROUP BY comment_id, reaction).
38
+ await db.schema
39
+ .createIndex("idx_comment_reactions_comment")
40
+ .ifNotExists()
41
+ .on("_emdash_comment_reactions")
42
+ .column("comment_id")
43
+ .execute();
44
+
45
+ // Per-voter rate limiting (recent reactions by a voter).
46
+ await db.schema
47
+ .createIndex("idx_comment_reactions_voter")
48
+ .ifNotExists()
49
+ .on("_emdash_comment_reactions")
50
+ .columns(["voter_hash", "created_at"])
51
+ .execute();
52
+ }
53
+
54
+ export async function down(db: Kysely<unknown>): Promise<void> {
55
+ await db.schema.dropTable("_emdash_comment_reactions").execute();
56
+ }