emdash 0.18.0 → 0.20.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 (528) hide show
  1. package/dist/{adapters-C5AWLJSD.d.mts → adapters-BzIHV3sw.d.mts} +1 -1
  2. package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BzIHV3sw.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-B1u7Qnvg.mjs} +2 -2
  4. package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-B1u7Qnvg.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +15 -15
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-Cs7DAACP.mjs → api-DStv36ik.mjs} +123 -20
  10. package/dist/api-DStv36ik.mjs.map +1 -0
  11. package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-DPfhPu5V.mjs} +2 -2
  12. package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-DPfhPu5V.mjs.map} +1 -1
  13. package/dist/{apply-BWMV4Zmw.mjs → apply-Dr7snAMT.mjs} +23 -23
  14. package/dist/apply-Dr7snAMT.mjs.map +1 -0
  15. package/dist/astro/index.d.mts +10 -10
  16. package/dist/astro/index.d.mts.map +1 -1
  17. package/dist/astro/index.mjs +115 -25
  18. package/dist/astro/index.mjs.map +1 -1
  19. package/dist/astro/middleware/auth.d.mts +9 -9
  20. package/dist/astro/middleware/auth.mjs +6 -6
  21. package/dist/astro/middleware/redirect.mjs +4 -4
  22. package/dist/astro/middleware/request-context.mjs +2 -2
  23. package/dist/astro/middleware/setup.mjs +1 -1
  24. package/dist/astro/middleware.d.mts +26 -4
  25. package/dist/astro/middleware.d.mts.map +1 -1
  26. package/dist/astro/middleware.mjs +242 -259
  27. package/dist/astro/middleware.mjs.map +1 -1
  28. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  29. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  30. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  31. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  32. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  34. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  35. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  36. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  37. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  38. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  39. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  40. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  41. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  42. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  43. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  44. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  45. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  46. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  47. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  48. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  49. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +33 -33
  51. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +33 -33
  52. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +33 -33
  53. package/dist/astro/routes/api/admin/plugins/index.mjs +33 -33
  54. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  55. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +33 -33
  56. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +33 -33
  57. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +33 -33
  58. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +33 -33
  59. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +34 -34
  60. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +33 -33
  61. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +34 -34
  62. package/dist/astro/routes/api/admin/plugins/updates.mjs +33 -33
  63. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +33 -33
  64. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  65. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +33 -33
  66. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  67. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  68. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  69. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  70. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  71. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  72. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  73. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  74. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  75. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  76. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  77. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  78. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  79. package/dist/astro/routes/api/auth/me.mjs +6 -6
  80. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  81. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  82. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  83. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  84. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  85. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
  86. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  87. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  88. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  89. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  90. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  91. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  92. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  93. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
  95. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  96. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  98. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
  99. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
  100. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  101. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  103. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  104. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
  105. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  106. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  107. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  108. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
  109. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  110. package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
  111. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  112. package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
  113. package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
  114. package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
  115. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
  116. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  117. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  118. package/dist/astro/routes/api/dashboard.mjs +7 -7
  119. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  120. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  121. package/dist/astro/routes/api/import/probe.mjs +6 -6
  122. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  123. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  124. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  125. package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
  126. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  127. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  128. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  129. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  130. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  131. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  132. package/dist/astro/routes/api/manifest.mjs +4 -4
  133. package/dist/astro/routes/api/mcp.mjs +29 -29
  134. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  135. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  136. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  137. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  138. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  139. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  140. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  141. package/dist/astro/routes/api/media.mjs +8 -8
  142. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  143. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  144. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  145. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  146. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  147. package/dist/astro/routes/api/menus/index.mjs +7 -7
  148. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  149. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  150. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  151. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  152. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  153. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  154. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  155. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  156. package/dist/astro/routes/api/openapi.json.mjs +17 -3
  157. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  158. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  159. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  160. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  161. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  162. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  163. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  164. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  165. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +33 -33
  166. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +33 -33
  167. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +33 -33
  168. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +33 -33
  169. package/dist/astro/routes/api/schema/collections/index.mjs +33 -33
  170. package/dist/astro/routes/api/schema/index.mjs +9 -14
  171. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  172. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +33 -33
  173. package/dist/astro/routes/api/schema/orphans/index.mjs +33 -33
  174. package/dist/astro/routes/api/search/enable.mjs +9 -9
  175. package/dist/astro/routes/api/search/index.mjs +8 -8
  176. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  177. package/dist/astro/routes/api/search/stats.mjs +6 -6
  178. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  179. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  180. package/dist/astro/routes/api/sections/index.mjs +8 -8
  181. package/dist/astro/routes/api/settings/email.mjs +5 -5
  182. package/dist/astro/routes/api/settings.mjs +12 -12
  183. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  184. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  185. package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -23
  186. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  187. package/dist/astro/routes/api/setup/index.mjs +23 -23
  188. package/dist/astro/routes/api/setup/status.mjs +4 -4
  189. package/dist/astro/routes/api/snapshot.mjs +6 -6
  190. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  191. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  192. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  193. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  194. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  195. package/dist/astro/routes/api/typegen.mjs +5 -5
  196. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  197. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  198. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  199. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  200. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
  201. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  202. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
  203. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  204. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -5
  205. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  206. package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
  207. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  208. package/dist/astro/routes/api/widget-components.mjs +3 -3
  209. package/dist/astro/routes/robots.txt.mjs +7 -7
  210. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  211. package/dist/astro/routes/sitemap-_collection_.xml.mjs +16 -9
  212. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  213. package/dist/astro/routes/sitemap.xml.mjs +8 -8
  214. package/dist/astro/types.d.mts +19 -12
  215. package/dist/astro/types.d.mts.map +1 -1
  216. package/dist/auth/providers/github.d.mts +1 -1
  217. package/dist/auth/providers/google.d.mts +1 -1
  218. package/dist/{authorize-CotM4Yiu.mjs → authorize-DsMSVSaY.mjs} +2 -2
  219. package/dist/{authorize-CotM4Yiu.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
  220. package/dist/{byline-CWQ9aSoz.mjs → byline-DUx48sJp.mjs} +6 -6
  221. package/dist/{byline-CWQ9aSoz.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
  222. package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields--WxSNS79.mjs} +2 -2
  223. package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
  224. package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-8TMtkBnH.mjs} +3 -3
  225. package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
  226. package/dist/{byline-fields-BNy7Ng1U.d.mts → byline-fields-DbibsvTl.d.mts} +30 -2
  227. package/dist/byline-fields-DbibsvTl.d.mts.map +1 -0
  228. package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
  229. package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
  230. package/dist/{bylines-LJMgENMI.mjs → bylines-BdxWCnPL.mjs} +3 -3
  231. package/dist/{bylines-LJMgENMI.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
  232. package/dist/{bylines-BJSva1Un.mjs → bylines-s8c2DXbH.mjs} +50 -6
  233. package/dist/{bylines-BJSva1Un.mjs.map → bylines-s8c2DXbH.mjs.map} +1 -1
  234. package/dist/{cache-lZL7SgVb.mjs → cache-B_HzASVT.mjs} +3 -3
  235. package/dist/{cache-lZL7SgVb.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
  236. package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-DXX3rfdI.mjs} +1 -1
  237. package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-DXX3rfdI.mjs.map} +1 -1
  238. package/dist/{chunks-BU-vP9Dh.mjs → chunks-BerYVuve.mjs} +2 -2
  239. package/dist/{chunks-BU-vP9Dh.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
  240. package/dist/cli/index.mjs +46 -32
  241. package/dist/cli/index.mjs.map +1 -1
  242. package/dist/client/cf-access.d.mts +1 -1
  243. package/dist/client/index.d.mts +1 -1
  244. package/dist/client/index.mjs +1 -1
  245. package/dist/{comment-C4jVbCM8.mjs → comment-sqQxNpN3.mjs} +2 -2
  246. package/dist/{comment-C4jVbCM8.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
  247. package/dist/{comments-BTAbC0Ek.mjs → comments-Vkivawyl.mjs} +3 -3
  248. package/dist/{comments-BTAbC0Ek.mjs.map → comments-Vkivawyl.mjs.map} +1 -1
  249. package/dist/{components-CTfpu3PZ.mjs → components-CK0cuUoH.mjs} +1 -1
  250. package/dist/{components-CTfpu3PZ.mjs.map → components-CK0cuUoH.mjs.map} +1 -1
  251. package/dist/{content-CyqOmOzm.mjs → content-BIlVx-RX.mjs} +132 -43
  252. package/dist/content-BIlVx-RX.mjs.map +1 -0
  253. package/dist/{context-DZ7bEh5-.mjs → context-Y7BRkWes.mjs} +10 -10
  254. package/dist/{context-DZ7bEh5-.mjs.map → context-Y7BRkWes.mjs.map} +1 -1
  255. package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
  256. package/dist/cron-BJ2ClIlj.mjs.map +1 -0
  257. package/dist/{dashboard-B5WQpNTP.mjs → dashboard-2JgAMWxK.mjs} +4 -4
  258. package/dist/{dashboard-B5WQpNTP.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
  259. package/dist/database/instrumentation.d.mts +10 -1
  260. package/dist/database/instrumentation.d.mts.map +1 -1
  261. package/dist/database/instrumentation.mjs +13 -1
  262. package/dist/database/instrumentation.mjs.map +1 -1
  263. package/dist/db/index.d.mts +3 -3
  264. package/dist/db/index.mjs +1 -1
  265. package/dist/db/libsql.d.mts +1 -1
  266. package/dist/db/postgres.d.mts +1 -1
  267. package/dist/db/sqlite.d.mts +1 -1
  268. package/dist/{default-xLFNSsZ9.mjs → default-IlBaTFxM.mjs} +1 -1
  269. package/dist/{default-xLFNSsZ9.mjs.map → default-IlBaTFxM.mjs.map} +1 -1
  270. package/dist/{device-flow-ptLrVINd.mjs → device-flow-R23SIbQ2.mjs} +5 -5
  271. package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-R23SIbQ2.mjs.map} +1 -1
  272. package/dist/{error-DJOsMVSt.mjs → error-RwM4dD35.mjs} +2 -2
  273. package/dist/{error-DJOsMVSt.mjs.map → error-RwM4dD35.mjs.map} +1 -1
  274. package/dist/{escape-bIyGoW5W.mjs → escape-Ds07EEyu.mjs} +1 -1
  275. package/dist/{escape-bIyGoW5W.mjs.map → escape-Ds07EEyu.mjs.map} +1 -1
  276. package/dist/{fts-manager-DR1ERA0c.mjs → fts-manager-1RgHmopc.mjs} +2 -2
  277. package/dist/{fts-manager-DR1ERA0c.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
  278. package/dist/{index-CjKdMZ3U.d.mts → index-B1keaX5Y.d.mts} +237 -24
  279. package/dist/index-B1keaX5Y.d.mts.map +1 -0
  280. package/dist/{index-D60_SzHG.d.mts → index-DR56od45.d.mts} +3 -3
  281. package/dist/{index-D60_SzHG.d.mts.map → index-DR56od45.d.mts.map} +1 -1
  282. package/dist/index.d.mts +17 -17
  283. package/dist/index.mjs +46 -46
  284. package/dist/{load-6ZrRhepW.mjs → load-BBetCvLC.mjs} +2 -2
  285. package/dist/{load-6ZrRhepW.mjs.map → load-BBetCvLC.mjs.map} +1 -1
  286. package/dist/{loader-Dyx8dhFV.mjs → loader-ZN1ll-d-.mjs} +36 -37
  287. package/dist/loader-ZN1ll-d-.mjs.map +1 -0
  288. package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-BtwbL_vj.mjs} +55 -2
  289. package/dist/manifest-schema-BtwbL_vj.mjs.map +1 -0
  290. package/dist/media/index.d.mts +1 -1
  291. package/dist/media/index.mjs +1 -1
  292. package/dist/media/local-runtime.d.mts +11 -11
  293. package/dist/media/local-runtime.mjs +6 -6
  294. package/dist/{media-C-oovGCG.mjs → media-JOf3pNkw.mjs} +2 -2
  295. package/dist/{media-C-oovGCG.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
  296. package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-Dknq-OFY.mjs} +1 -1
  297. package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-Dknq-OFY.mjs.map} +1 -1
  298. package/dist/media-url-VClf8glU.mjs +26 -0
  299. package/dist/media-url-VClf8glU.mjs.map +1 -0
  300. package/dist/{menus-DugoYwTX.mjs → menus-DX4_E01q.mjs} +3 -3
  301. package/dist/{menus-DugoYwTX.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
  302. package/dist/{menus-BKkxXCmd.mjs → menus-DrQLusqj.mjs} +87 -37
  303. package/dist/menus-DrQLusqj.mjs.map +1 -0
  304. package/dist/{mode-BjlXswIw.mjs → mode-CO2vQHfq.mjs} +1 -1
  305. package/dist/{mode-BjlXswIw.mjs.map → mode-CO2vQHfq.mjs.map} +1 -1
  306. package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
  307. package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
  308. package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-Bw4NdF_S.mjs} +5 -5
  309. package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-Bw4NdF_S.mjs.map} +1 -1
  310. package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BGGFp57s.mjs} +1 -1
  311. package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BGGFp57s.mjs.map} +1 -1
  312. package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-97x0xtN2.mjs} +1 -1
  313. package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-97x0xtN2.mjs.map} +1 -1
  314. package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-B_vnZHKO.mjs} +1 -1
  315. package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-B_vnZHKO.mjs.map} +1 -1
  316. package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
  317. package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
  318. package/dist/{options-tb7DJROi.d.mts → options-DyYIYpPd.d.mts} +3 -3
  319. package/dist/{options-tb7DJROi.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
  320. package/dist/page/index.d.mts +2 -2
  321. package/dist/{parse-BBkFmLVr.mjs → parse-CrGndy1A.mjs} +2 -2
  322. package/dist/{parse-BBkFmLVr.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
  323. package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-C3QgnQnU.mjs} +1 -1
  324. package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-C3QgnQnU.mjs.map} +1 -1
  325. package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
  326. package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
  327. package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-CVBv5z8k.d.mts} +1 -1
  328. package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-CVBv5z8k.d.mts.map} +1 -1
  329. package/dist/plugin-types.d.mts +1 -1
  330. package/dist/plugin-utils.d.mts +9 -9
  331. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  332. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  333. package/dist/{public-url-egRHCy1m.mjs → public-url-BFVC2OTJ.mjs} +1 -1
  334. package/dist/{public-url-egRHCy1m.mjs.map → public-url-BFVC2OTJ.mjs.map} +1 -1
  335. package/dist/{query-Ctlq1aOk.mjs → query-CbUcI4Xk.mjs} +33 -17
  336. package/dist/query-CbUcI4Xk.mjs.map +1 -0
  337. package/dist/{rate-limit-CH6W6ikK.mjs → rate-limit-C7hjdkS5.mjs} +2 -2
  338. package/dist/{rate-limit-CH6W6ikK.mjs.map → rate-limit-C7hjdkS5.mjs.map} +1 -1
  339. package/dist/{redirect-Cw3JTlmj.mjs → redirect-B_q19j4v.mjs} +1 -1
  340. package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-B_q19j4v.mjs.map} +1 -1
  341. package/dist/{redirect-C6tJA7tk.mjs → redirect-CRWIt8Zj.mjs} +3 -3
  342. package/dist/{redirect-C6tJA7tk.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
  343. package/dist/{redirects-C0L9JUk4.mjs → redirects-CCbCqCCd.mjs} +28 -4
  344. package/dist/redirects-CCbCqCCd.mjs.map +1 -0
  345. package/dist/{redirects-CacE9eQa.mjs → redirects-DxVoR7PI.mjs} +5 -5
  346. package/dist/{redirects-CacE9eQa.mjs.map → redirects-DxVoR7PI.mjs.map} +1 -1
  347. package/dist/{registry-CIDxZbhh.mjs → registry-brYh-rAT.mjs} +6 -6
  348. package/dist/{registry-CIDxZbhh.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
  349. package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
  350. package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
  351. package/dist/request-context.d.mts +7 -0
  352. package/dist/request-context.d.mts.map +1 -1
  353. package/dist/request-context.mjs +2 -1
  354. package/dist/request-context.mjs.map +1 -1
  355. package/dist/{runner-pt6Wl-l-.mjs → runner--4wMWwKM.mjs} +217 -166
  356. package/dist/runner--4wMWwKM.mjs.map +1 -0
  357. package/dist/{runner-DM1yR5qd.d.mts → runner-DTdhuI9i.d.mts} +2 -2
  358. package/dist/{runner-DM1yR5qd.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
  359. package/dist/runtime.d.mts +10 -10
  360. package/dist/runtime.mjs +2 -2
  361. package/dist/{schema-B4tk0HAG.mjs → schema-C1E70ug_.mjs} +5 -5
  362. package/dist/{schema-B4tk0HAG.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
  363. package/dist/{search-f-fNfwab.mjs → search-B3SGZw91.mjs} +4 -4
  364. package/dist/{search-f-fNfwab.mjs.map → search-B3SGZw91.mjs.map} +1 -1
  365. package/dist/{secrets-YYbTgB1w.mjs → secrets-ChPTmy9x.mjs} +2 -2
  366. package/dist/{secrets-YYbTgB1w.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
  367. package/dist/{sections-biElLfT9.mjs → sections-D_lVzwRZ.mjs} +3 -3
  368. package/dist/{sections-biElLfT9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
  369. package/dist/seed/index.d.mts +2 -2
  370. package/dist/seed/index.mjs +17 -17
  371. package/dist/seo/index.d.mts +1 -1
  372. package/dist/seo/index.d.mts.map +1 -1
  373. package/dist/seo/index.mjs +3 -12
  374. package/dist/seo/index.mjs.map +1 -1
  375. package/dist/{seo-BR39kvTF.mjs → seo-B5e6y9Wk.mjs} +2 -2
  376. package/dist/{seo-BR39kvTF.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
  377. package/dist/{seo-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
  378. package/dist/seo-D_LPkOtu.mjs.map +1 -0
  379. package/dist/{service-BhR2acnc.mjs → service-ChDcsTBs.mjs} +3 -3
  380. package/dist/{service-BhR2acnc.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
  381. package/dist/{settings-D_NJvjgN.mjs → settings-Cv47v9u8.mjs} +3 -3
  382. package/dist/{settings-D_NJvjgN.mjs.map → settings-Cv47v9u8.mjs.map} +1 -1
  383. package/dist/settings-DfxiWY_s.mjs +411 -0
  384. package/dist/settings-DfxiWY_s.mjs.map +1 -0
  385. package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-yvPE4OsP.mjs} +2 -2
  386. package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-yvPE4OsP.mjs.map} +1 -1
  387. package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-C9aFzb94.mjs} +1 -1
  388. package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-C9aFzb94.mjs.map} +1 -1
  389. package/dist/{site-url-Cm8-sJy7.mjs → site-url-CnHlmAs9.mjs} +2 -2
  390. package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-CnHlmAs9.mjs.map} +1 -1
  391. package/dist/storage/local.d.mts +1 -1
  392. package/dist/storage/s3.d.mts +1 -1
  393. package/dist/{taxonomies-Mhn9rjTQ.mjs → taxonomies-BILwiyGk.mjs} +4 -4
  394. package/dist/{taxonomies-Mhn9rjTQ.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
  395. package/dist/{taxonomies-Crtzy4MT.mjs → taxonomies-BdAmbOwx.mjs} +50 -12
  396. package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
  397. package/dist/{taxonomy-DTZrIQpi.mjs → taxonomy-CdllE4oq.mjs} +3 -3
  398. package/dist/{taxonomy-DTZrIQpi.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
  399. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
  400. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
  401. package/dist/{transport-OnMNbsIA.d.mts → transport-B7PPP2CC.d.mts} +1 -1
  402. package/dist/{transport-OnMNbsIA.d.mts.map → transport-B7PPP2CC.d.mts.map} +1 -1
  403. package/dist/{transport--Ck3RBin.mjs → transport-CmpLD7W3.mjs} +1 -1
  404. package/dist/{transport--Ck3RBin.mjs.map → transport-CmpLD7W3.mjs.map} +1 -1
  405. package/dist/{types-DWnN7weG.d.mts → types-BFgrqwSk.d.mts} +1 -1
  406. package/dist/{types-DWnN7weG.d.mts.map → types-BFgrqwSk.d.mts.map} +1 -1
  407. package/dist/{types-Qa7-HJJC.d.mts → types-BH8-30hc.d.mts} +1 -1
  408. package/dist/{types-Qa7-HJJC.d.mts.map → types-BH8-30hc.d.mts.map} +1 -1
  409. package/dist/{types-DawhLFwy.d.mts → types-BPzXTV9x.d.mts} +26 -1
  410. package/dist/{types-DawhLFwy.d.mts.map → types-BPzXTV9x.d.mts.map} +1 -1
  411. package/dist/{types-DbCWhHet.d.mts → types-BUUVn1zr.d.mts} +2 -2
  412. package/dist/types-BUUVn1zr.d.mts.map +1 -0
  413. package/dist/{types-K3MDsxpy.mjs → types-BXSUSAjt.mjs} +16 -3
  414. package/dist/{types-K3MDsxpy.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
  415. package/dist/{types-DMwSpvcw.d.mts → types-CPAPl93j.d.mts} +9 -3
  416. package/dist/{types-DMwSpvcw.d.mts.map → types-CPAPl93j.d.mts.map} +1 -1
  417. package/dist/types-CZI4E3qG.mjs +3 -0
  418. package/dist/{types-kwqCOUxj.d.mts → types-D4kUqbHh.d.mts} +1 -1
  419. package/dist/{types-kwqCOUxj.d.mts.map → types-D4kUqbHh.d.mts.map} +1 -1
  420. package/dist/{types-i8_uzhMD.d.mts → types-DTniiNto.d.mts} +19 -4
  421. package/dist/types-DTniiNto.d.mts.map +1 -0
  422. package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
  423. package/dist/types-DZk_y-MU.mjs.map +1 -0
  424. package/dist/{types-DX6v9KzJ.d.mts → types-S15DXXNi.d.mts} +1 -1
  425. package/dist/{types-DX6v9KzJ.d.mts.map → types-S15DXXNi.d.mts.map} +1 -1
  426. package/dist/{user-DzEUl5zA.mjs → user-C0um7wrg.mjs} +18 -2
  427. package/dist/user-C0um7wrg.mjs.map +1 -0
  428. package/dist/{validate-JCXcsqiY.mjs → validate-Bz4vqcX1.mjs} +6 -3
  429. package/dist/validate-Bz4vqcX1.mjs.map +1 -0
  430. package/dist/{validate-Dy6nkNls.d.mts → validate-CNwkPWzz.d.mts} +13 -5
  431. package/dist/validate-CNwkPWzz.d.mts.map +1 -0
  432. package/dist/{validation-Bq-VyKJg.mjs → validation-DgGTJm3u.mjs} +5 -5
  433. package/dist/{validation-Bq-VyKJg.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
  434. package/dist/version-D-5txk2m.mjs +7 -0
  435. package/dist/{version-CnS-Cr8A.mjs.map → version-D-5txk2m.mjs.map} +1 -1
  436. package/dist/{widgets-Bap1eS1X.mjs → widgets-DZfmAbE4.mjs} +47 -44
  437. package/dist/widgets-DZfmAbE4.mjs.map +1 -0
  438. package/dist/{zod-generator-BSDpkqSH.mjs → zod-generator-Djo_VHCt.mjs} +2 -2
  439. package/dist/{zod-generator-BSDpkqSH.mjs.map → zod-generator-Djo_VHCt.mjs.map} +1 -1
  440. package/package.json +10 -10
  441. package/src/api/handlers/content.ts +107 -8
  442. package/src/api/handlers/index.ts +2 -0
  443. package/src/api/handlers/marketplace.ts +2 -5
  444. package/src/api/handlers/registry.ts +70 -0
  445. package/src/api/handlers/seo.ts +9 -1
  446. package/src/api/openapi/document.ts +25 -0
  447. package/src/api/schemas/content.ts +33 -0
  448. package/src/api/schemas/schema.ts +13 -1
  449. package/src/astro/integration/index.ts +98 -0
  450. package/src/astro/integration/routes.ts +6 -0
  451. package/src/astro/integration/virtual-modules.ts +39 -0
  452. package/src/astro/integration/vite-config.ts +12 -0
  453. package/src/astro/middleware.ts +48 -6
  454. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  455. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
  456. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
  457. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
  458. package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
  459. package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
  460. package/src/astro/routes/api/schema/index.ts +7 -15
  461. package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
  462. package/src/astro/types.ts +8 -1
  463. package/src/bylines/index.ts +57 -0
  464. package/src/cli/commands/bundle-utils.ts +2 -0
  465. package/src/cli/commands/export-seed.ts +28 -12
  466. package/src/cli/commands/secrets.ts +2 -2
  467. package/src/components/EmDashImage.astro +22 -4
  468. package/src/components/Image.astro +20 -3
  469. package/src/database/instrumentation.ts +13 -0
  470. package/src/database/migrations/043_content_references.ts +121 -0
  471. package/src/database/migrations/runner.ts +2 -0
  472. package/src/database/repositories/content.ts +225 -67
  473. package/src/database/repositories/index.ts +7 -0
  474. package/src/database/repositories/relation.ts +467 -0
  475. package/src/database/repositories/types.ts +31 -0
  476. package/src/database/repositories/user.ts +18 -0
  477. package/src/database/types.ts +34 -0
  478. package/src/emdash-runtime.ts +172 -67
  479. package/src/index.ts +8 -1
  480. package/src/loader.ts +81 -39
  481. package/src/media/responsive.ts +125 -0
  482. package/src/plugins/cron.ts +3 -2
  483. package/src/plugins/index.ts +5 -0
  484. package/src/plugins/manifest-schema.ts +75 -0
  485. package/src/plugins/marketplace.ts +2 -5
  486. package/src/plugins/scheduler/node.ts +9 -2
  487. package/src/plugins/types.ts +12 -0
  488. package/src/query.ts +45 -7
  489. package/src/request-context.ts +8 -0
  490. package/src/scheduled-publish.ts +153 -0
  491. package/src/schema/types.ts +11 -1
  492. package/src/seed/apply.ts +16 -6
  493. package/src/seed/types.ts +9 -0
  494. package/src/seed/validate.ts +15 -0
  495. package/src/seo/index.ts +2 -28
  496. package/src/seo/media-url.ts +32 -0
  497. package/src/settings/index.ts +32 -40
  498. package/src/taxonomies/index.ts +79 -12
  499. package/src/utils/isolate-cache.ts +189 -0
  500. package/src/virtual-modules.d.ts +11 -0
  501. package/src/widgets/index.ts +57 -54
  502. package/dist/api-Cs7DAACP.mjs.map +0 -1
  503. package/dist/apply-BWMV4Zmw.mjs.map +0 -1
  504. package/dist/byline-fields-BNy7Ng1U.d.mts.map +0 -1
  505. package/dist/content-CyqOmOzm.mjs.map +0 -1
  506. package/dist/cron-DZovZUnC.mjs.map +0 -1
  507. package/dist/index-CjKdMZ3U.d.mts.map +0 -1
  508. package/dist/loader-Dyx8dhFV.mjs.map +0 -1
  509. package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
  510. package/dist/menus-BKkxXCmd.mjs.map +0 -1
  511. package/dist/query-Ctlq1aOk.mjs.map +0 -1
  512. package/dist/redirects-C0L9JUk4.mjs.map +0 -1
  513. package/dist/runner-pt6Wl-l-.mjs.map +0 -1
  514. package/dist/seo-DfjLvu8i.mjs.map +0 -1
  515. package/dist/settings-b5zW1R1T.mjs +0 -235
  516. package/dist/settings-b5zW1R1T.mjs.map +0 -1
  517. package/dist/taxonomies-Crtzy4MT.mjs.map +0 -1
  518. package/dist/types-Cj2S6FuC.mjs +0 -3
  519. package/dist/types-D8bhH891.mjs.map +0 -1
  520. package/dist/types-DbCWhHet.d.mts.map +0 -1
  521. package/dist/types-i8_uzhMD.d.mts.map +0 -1
  522. package/dist/user-DzEUl5zA.mjs.map +0 -1
  523. package/dist/validate-Dy6nkNls.d.mts.map +0 -1
  524. package/dist/validate-JCXcsqiY.mjs.map +0 -1
  525. package/dist/version-CnS-Cr8A.mjs +0 -7
  526. package/dist/widgets-Bap1eS1X.mjs.map +0 -1
  527. package/src/plugins/scheduler/piggyback.ts +0 -71
  528. /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-Oq39ba-Z.mjs} +0 -0
@@ -18,11 +18,16 @@
18
18
  */
19
19
  import type { MediaValue } from "../fields/types.js";
20
20
  import type { HTMLAttributes } from "astro/types";
21
+ import { getImage } from "astro:assets";
21
22
  import type { ImageEmbed } from "../media/types.js";
22
23
  import { getMediaProvider } from "../media/provider-loader.js";
23
24
  import { buildRenderMediaUrl } from "../media/url.js";
24
- // Standard responsive breakpoints
25
- const BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
25
+ import {
26
+ buildResponsiveImage,
27
+ toAbsoluteMediaUrl,
28
+ RESPONSIVE_BREAKPOINTS,
29
+ } from "../media/responsive.js";
30
+ import { getPublicOrigin } from "../api/public-url.js";
26
31
 
27
32
  interface Props extends Omit<
28
33
  HTMLAttributes<"img">,
@@ -72,7 +77,7 @@ function generateSrcset(
72
77
  maxWidth: number,
73
78
  aspectRatio?: number
74
79
  ): string {
75
- return BREAKPOINTS.filter((w) => w <= maxWidth * 2) // Include up to 2x for retina
80
+ return RESPONSIVE_BREAKPOINTS.filter((w) => w <= maxWidth * 2) // Include up to 2x for retina
76
81
  .map((w) => {
77
82
  const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
78
83
  return `${getSrc({ width: w, height: h })} ${w}w`;
@@ -98,8 +103,21 @@ if (img) {
98
103
  const providerId = img.provider ?? "local";
99
104
 
100
105
  if (providerId === "local" || img.src) {
101
- // Local provider or direct src URL
106
+ // Local provider or direct src URL. Route through Astro's image service
107
+ // (`astro:assets`) to generate a responsive srcset; on Cloudflare this is
108
+ // the Images binding, on Node it is sharp. Falls back to a plain <img>
109
+ // when the service is unavailable or the source can't be optimized.
102
110
  src = img.src || buildLocalImageUrl(img);
111
+ const optimized = await buildResponsiveImage(getImage, {
112
+ src: toAbsoluteMediaUrl(src, getPublicOrigin(Astro.url, Astro.locals.emdash?.config)),
113
+ width: finalWidth,
114
+ height: finalHeight,
115
+ });
116
+ if (optimized) {
117
+ src = optimized.src;
118
+ srcset = optimized.srcset;
119
+ sizes = optimized.sizes;
120
+ }
103
121
  } else {
104
122
  // External provider
105
123
  try {
@@ -5,11 +5,16 @@
5
5
  * Renders image blocks from WordPress imports and EmDash media.
6
6
  * Uses the provider's getSrc function for responsive srcset generation.
7
7
  */
8
+ import { getImage } from "astro:assets";
8
9
  import type { ImageEmbed } from "../media/types.js";
9
10
  import { getMediaProvider } from "../media/provider-loader.js";
10
11
  import { buildRenderMediaUrl } from "../media/url.js";
11
- // Standard responsive breakpoints
12
- const BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
12
+ import {
13
+ buildResponsiveImage,
14
+ toAbsoluteMediaUrl,
15
+ RESPONSIVE_BREAKPOINTS,
16
+ } from "../media/responsive.js";
17
+ import { getPublicOrigin } from "../api/public-url.js";
13
18
 
14
19
  export interface Props {
15
20
  node: {
@@ -44,7 +49,7 @@ function generateSrcset(
44
49
  maxWidth: number,
45
50
  aspectRatio?: number,
46
51
  ): string {
47
- return BREAKPOINTS.filter((w) => w <= maxWidth * 2)
52
+ return RESPONSIVE_BREAKPOINTS.filter((w) => w <= maxWidth * 2)
48
53
  .map((w) => {
49
54
  const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
50
55
  return `${getSrc({ width: w, height: h })} ${w}w`;
@@ -131,6 +136,18 @@ if (!src) {
131
136
  url: asset.url,
132
137
  id: asset._ref,
133
138
  });
139
+ // Generate a responsive srcset via Astro's image service for local/R2 media.
140
+ // Falls back to the plain URL when optimization isn't possible.
141
+ const optimized = await buildResponsiveImage(getImage, {
142
+ src: toAbsoluteMediaUrl(src, getPublicOrigin(Astro.url, Astro.locals.emdash?.config)),
143
+ width: renderWidth,
144
+ height: renderHeight,
145
+ });
146
+ if (optimized) {
147
+ src = optimized.src;
148
+ srcset = optimized.srcset;
149
+ sizes = optimized.sizes;
150
+ }
134
151
  }
135
152
 
136
153
  // Build placeholder background style
@@ -110,3 +110,16 @@ function kyselyLog(event: LogEvent): void {
110
110
  export function kyselyLogOption(): Logger {
111
111
  return kyselyLog;
112
112
  }
113
+
114
+ /**
115
+ * Record physical database round trips for the current request.
116
+ *
117
+ * Called by backends that batch (the DO SQL driver coalesces same-turn SELECTs
118
+ * into one RPC), so we can see round-trip count separately from logical query
119
+ * count (`dbCount`, bumped by the Kysely log hook). No-op outside a request or
120
+ * when metrics aren't attached (e.g. migrations on the singleton).
121
+ */
122
+ export function recordRpc(count = 1): void {
123
+ const ctx = getRequestContext();
124
+ if (ctx?.metrics) ctx.metrics.rpcCount += count;
125
+ }
@@ -0,0 +1,121 @@
1
+ import type { Kysely } from "kysely";
2
+
3
+ import { getI18nConfig } from "../../i18n/config.js";
4
+ import { currentTimestamp } from "../dialect-helpers.js";
5
+
6
+ /**
7
+ * Content references.
8
+ *
9
+ * `_emdash_relations` defines relationship types (row-per-locale, mirroring
10
+ * `_emdash_taxonomy_defs`): which collection is the parent, which is the child
11
+ * (the side that may multiply), and localized labels for each role.
12
+ *
13
+ * `_emdash_content_references` holds directed `parent → child` edges between
14
+ * content entries. Both endpoints and the relation are referenced by
15
+ * `translation_group`, so edges are locale-agnostic. As with
16
+ * `content_taxonomies`, group-linking precludes SQL foreign keys; referential
17
+ * cleanup is an application-layer concern.
18
+ *
19
+ * Idempotency: every `CREATE TABLE` and `CREATE INDEX` uses `.ifNotExists()`,
20
+ * so a partial prior run (a crash mid-migration, or a retry after the runner's
21
+ * race-recovery path) re-applies cleanly — including any indexes that landed in
22
+ * the failed pass after their table.
23
+ */
24
+ export async function up(db: Kysely<unknown>): Promise<void> {
25
+ const defaultLocale = getI18nConfig()?.defaultLocale ?? "en";
26
+
27
+ await db.schema
28
+ .createTable("_emdash_relations")
29
+ .ifNotExists()
30
+ .addColumn("id", "text", (c) => c.primaryKey())
31
+ .addColumn("name", "text", (c) => c.notNull())
32
+ .addColumn("parent_collection", "text", (c) => c.notNull())
33
+ .addColumn("child_collection", "text", (c) => c.notNull())
34
+ .addColumn("parent_label", "text", (c) => c.notNull())
35
+ .addColumn("child_label", "text", (c) => c.notNull())
36
+ .addColumn("locale", "text", (c) => c.notNull().defaultTo(defaultLocale))
37
+ .addColumn("translation_group", "text", (c) => c.notNull())
38
+ .addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
39
+ .addColumn("updated_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
40
+ .addUniqueConstraint("_emdash_relations_name_locale_unique", ["name", "locale"])
41
+ .execute();
42
+
43
+ await db.schema
44
+ .createIndex("idx__emdash_relations_locale")
45
+ .ifNotExists()
46
+ .on("_emdash_relations")
47
+ .column("locale")
48
+ .execute();
49
+ await db.schema
50
+ .createIndex("idx__emdash_relations_translation_group")
51
+ .ifNotExists()
52
+ .on("_emdash_relations")
53
+ .column("translation_group")
54
+ .execute();
55
+ await db.schema
56
+ .createIndex("idx__emdash_relations_parent_collection")
57
+ .ifNotExists()
58
+ .on("_emdash_relations")
59
+ .column("parent_collection")
60
+ .execute();
61
+ await db.schema
62
+ .createIndex("idx__emdash_relations_child_collection")
63
+ .ifNotExists()
64
+ .on("_emdash_relations")
65
+ .column("child_collection")
66
+ .execute();
67
+
68
+ // One row per (translation_group, locale): the row-per-locale model wants a
69
+ // relation to have at most one variant per locale. Migration 040 enforces
70
+ // the same invariant for `_emdash_bylines` with a *partial* unique
71
+ // (`WHERE translation_group IS NOT NULL`) only because it back-fills an
72
+ // existing table; this table is new and `translation_group` is `NOT NULL`,
73
+ // so a plain unique index suffices.
74
+ await db.schema
75
+ .createIndex("idx__emdash_relations_group_locale_unique")
76
+ .ifNotExists()
77
+ .unique()
78
+ .on("_emdash_relations")
79
+ .columns(["translation_group", "locale"])
80
+ .execute();
81
+
82
+ await db.schema
83
+ .createTable("_emdash_content_references")
84
+ .ifNotExists()
85
+ .addColumn("id", "text", (c) => c.primaryKey())
86
+ .addColumn("relation_group", "text", (c) => c.notNull())
87
+ .addColumn("parent_group", "text", (c) => c.notNull())
88
+ .addColumn("child_group", "text", (c) => c.notNull())
89
+ .addColumn("sort_order", "integer", (c) => c.notNull().defaultTo(0))
90
+ .addColumn("created_at", "text", (c) => c.defaultTo(currentTimestamp(db)))
91
+ .addUniqueConstraint("content_references_unique", [
92
+ "relation_group",
93
+ "parent_group",
94
+ "child_group",
95
+ ])
96
+ .execute();
97
+
98
+ await db.schema
99
+ .createIndex("idx__emdash_content_references_parent")
100
+ .ifNotExists()
101
+ .on("_emdash_content_references")
102
+ .columns(["parent_group", "relation_group", "sort_order"])
103
+ .execute();
104
+ await db.schema
105
+ .createIndex("idx__emdash_content_references_child")
106
+ .ifNotExists()
107
+ .on("_emdash_content_references")
108
+ .columns(["child_group", "relation_group"])
109
+ .execute();
110
+ await db.schema
111
+ .createIndex("idx__emdash_content_references_relation")
112
+ .ifNotExists()
113
+ .on("_emdash_content_references")
114
+ .column("relation_group")
115
+ .execute();
116
+ }
117
+
118
+ export async function down(db: Kysely<unknown>): Promise<void> {
119
+ await db.schema.dropTable("_emdash_content_references").ifExists().execute();
120
+ await db.schema.dropTable("_emdash_relations").ifExists().execute();
121
+ }
@@ -44,6 +44,7 @@ import * as m039 from "./039_fix_fts5_triggers.js";
44
44
  import * as m040 from "./040_byline_i18n.js";
45
45
  import * as m041 from "./041_content_locale_list_index.js";
46
46
  import * as m042 from "./042_byline_fields.js";
47
+ import * as m043 from "./043_content_references.js";
47
48
 
48
49
  const MIGRATIONS: Readonly<Record<string, Migration>> = Object.freeze({
49
50
  "001_initial": m001,
@@ -87,6 +88,7 @@ const MIGRATIONS: Readonly<Record<string, Migration>> = Object.freeze({
87
88
  "040_byline_i18n": m040,
88
89
  "041_content_locale_list_index": m041,
89
90
  "042_byline_fields": m042,
91
+ "043_content_references": m043,
90
92
  });
91
93
 
92
94
  /** Total number of registered migrations. Exported for use in tests. */
@@ -11,8 +11,14 @@ import type {
11
11
  FindManyOptions,
12
12
  FindManyResult,
13
13
  ContentItem,
14
+ ContentDateField,
15
+ } from "./types.js";
16
+ import {
17
+ EmDashValidationError,
18
+ ScheduledNotDueError,
19
+ encodeCursor,
20
+ decodeCursor,
14
21
  } from "./types.js";
15
- import { EmDashValidationError, encodeCursor, decodeCursor } from "./types.js";
16
22
 
17
23
  // Regex pattern for ULID validation
18
24
  const ULID_PATTERN = /^[0-9A-Z]{26}$/;
@@ -20,6 +26,18 @@ const ULID_PATTERN = /^[0-9A-Z]{26}$/;
20
26
  // LIKE wildcards that must be escaped so user search input is matched literally.
21
27
  const LIKE_WILDCARD_RE = /[\\%_]/g;
22
28
 
29
+ /**
30
+ * Whitelist mapping a public date-filter field to its physical column. Keeping
31
+ * this separate from `mapOrderField` makes the filterable set explicit and
32
+ * prevents filtering on arbitrary columns.
33
+ */
34
+ const DATE_FILTER_COLUMNS: Record<ContentDateField, "created_at" | "updated_at" | "published_at"> =
35
+ {
36
+ createdAt: "created_at",
37
+ updatedAt: "updated_at",
38
+ publishedAt: "published_at",
39
+ };
40
+
23
41
  /**
24
42
  * System columns that exist in every ec_* table
25
43
  */
@@ -493,6 +511,7 @@ export class ContentRepository {
493
511
  }
494
512
 
495
513
  query = this.applySearchFilter(query, options.where);
514
+ query = this.applyDateFilter(query, options.where);
496
515
 
497
516
  // Handle cursor pagination — decodeCursor throws InvalidCursorError
498
517
  // on malformed input; let it propagate so handlers surface a
@@ -785,19 +804,35 @@ export class ContentRepository {
785
804
  );
786
805
  }
787
806
 
807
+ /**
808
+ * Apply the optional inclusive date-range filter. The field is mapped
809
+ * through `DATE_FILTER_COLUMNS` (a closed whitelist), and bounds compare
810
+ * lexicographically against the stored ISO 8601 timestamps. A `publishedAt`
811
+ * range naturally excludes never-published rows (their column is NULL).
812
+ */
813
+ private applyDateFilter<QB extends { where: (cb: (eb: any) => unknown) => QB }>(
814
+ query: QB,
815
+ where?: { dateFilter?: { field: string; from?: string; to?: string } },
816
+ ): QB {
817
+ const filter = where?.dateFilter;
818
+ if (!filter) return query;
819
+ const column = DATE_FILTER_COLUMNS[filter.field as ContentDateField];
820
+ if (!column) {
821
+ throw new EmDashValidationError(`Invalid date filter field: ${filter.field}`);
822
+ }
823
+ const { from, to } = filter;
824
+ if (!from && !to) return query;
825
+
826
+ let next = query;
827
+ if (from) next = next.where((eb) => eb(column as any, ">=", from));
828
+ if (to) next = next.where((eb) => eb(column as any, "<=", to));
829
+ return next;
830
+ }
831
+
788
832
  /**
789
833
  * Count content items
790
834
  */
791
- async count(
792
- type: string,
793
- where?: {
794
- status?: string;
795
- authorId?: string;
796
- locale?: string;
797
- q?: string;
798
- searchColumns?: string[];
799
- },
800
- ): Promise<number> {
835
+ async count(type: string, where?: FindManyOptions["where"]): Promise<number> {
801
836
  const tableName = getTableName(type);
802
837
 
803
838
  let query = this.db
@@ -818,11 +853,34 @@ export class ContentRepository {
818
853
  }
819
854
 
820
855
  query = this.applySearchFilter(query, where);
856
+ query = this.applyDateFilter(query, where);
821
857
 
822
858
  const result = await query.executeTakeFirst();
823
859
  return Number(result?.count || 0);
824
860
  }
825
861
 
862
+ /**
863
+ * Distinct, non-null `author_id` values across the collection's live
864
+ * (non-trashed) content. Used to populate the admin author filter with
865
+ * only the users who have actually authored entries, rather than the
866
+ * full user directory (which requires admin privileges to read).
867
+ */
868
+ async findDistinctAuthorIds(type: string): Promise<string[]> {
869
+ const tableName = getTableName(type);
870
+
871
+ const rows = await this.db
872
+ .selectFrom(tableName as keyof Database)
873
+ .select("author_id")
874
+ .distinct()
875
+ .where("deleted_at" as never, "is", null)
876
+ .where("author_id" as never, "is not", null)
877
+ .execute();
878
+
879
+ return rows
880
+ .map((row) => (row as { author_id: string | null }).author_id)
881
+ .filter((id): id is string => id !== null);
882
+ }
883
+
826
884
  // get overall statistics (total, published, draft) for a content type in a single query
827
885
  async getStats(type: string): Promise<{ total: number; published: number; draft: number }> {
828
886
  const tableName = getTableName(type);
@@ -933,17 +991,30 @@ export class ContentRepository {
933
991
  * Returns all content where scheduled_at <= now, regardless of status.
934
992
  * This covers both draft-scheduled posts (status='scheduled') and
935
993
  * published posts with scheduled draft changes (status='published').
994
+ *
995
+ * `limit` (optional) caps how many due rows are returned, oldest-due first.
996
+ * The scheduled-publishing sweep passes a limit so a large backlog can't
997
+ * fan out unbounded publish/webhook work in a single tick (and blow a Worker
998
+ * invocation's CPU/subrequest budget); the remainder drains on later ticks.
936
999
  */
937
- async findReadyToPublish(type: string): Promise<ContentItem[]> {
1000
+ async findReadyToPublish(type: string, limit?: number): Promise<ContentItem[]> {
938
1001
  const tableName = getTableName(type);
939
1002
  const now = new Date().toISOString();
940
1003
 
1004
+ // Embed an empty fragment when unbounded so callers that want every due
1005
+ // row (manual flows, tests) keep the original behaviour.
1006
+ const limitClause =
1007
+ typeof limit === "number" && Number.isInteger(limit) && limit > 0
1008
+ ? sql`LIMIT ${limit}`
1009
+ : sql``;
1010
+
941
1011
  const result = await sql<Record<string, unknown>>`
942
1012
  SELECT * FROM ${sql.ref(tableName)}
943
1013
  WHERE scheduled_at IS NOT NULL
944
1014
  AND scheduled_at <= ${now}
945
1015
  AND deleted_at IS NULL
946
1016
  ORDER BY scheduled_at ASC
1017
+ ${limitClause}
947
1018
  `.execute(this.db);
948
1019
 
949
1020
  return result.rows.map((row) => this.mapRow(type, row));
@@ -978,8 +1049,21 @@ export class ContentRepository {
978
1049
  * original date) and falls back to the current time on first publish. Pass
979
1050
  * an explicit value to backdate a publish (e.g. when migrating content from
980
1051
  * another CMS).
1052
+ *
1053
+ * `requireDue` (optional) gates the publish on the row still being due:
1054
+ * `scheduled_at` non-null and in the past. Used by the scheduled-publishing
1055
+ * sweep to avoid publishing content an editor unscheduled or rescheduled
1056
+ * between selection and publish. It claims the row with a single conditional
1057
+ * UPDATE (clearing `scheduled_at`) before any other write, so it is atomic
1058
+ * even on D1 (no multi-statement transactions) and serialises against
1059
+ * `unschedule()` and concurrent sweeps — no TOCTOU and no double publish.
981
1060
  */
982
- async publish(type: string, id: string, publishedAt?: string): Promise<ContentItem> {
1061
+ async publish(
1062
+ type: string,
1063
+ id: string,
1064
+ publishedAt?: string,
1065
+ requireDue = false,
1066
+ ): Promise<ContentItem> {
983
1067
  const tableName = getTableName(type);
984
1068
  const now = new Date().toISOString();
985
1069
 
@@ -988,71 +1072,145 @@ export class ContentRepository {
988
1072
  throw new EmDashValidationError("Content item not found");
989
1073
  }
990
1074
 
991
- const revisionRepo = new RevisionRepository(this.db);
992
- let revisionToPublish = existing.draftRevisionId || existing.liveRevisionId;
993
-
994
- if (!revisionToPublish) {
995
- // No revision exists - create one from current data
996
- const revision = await revisionRepo.create({
997
- collection: type,
998
- entryId: id,
999
- data: existing.data,
1000
- });
1001
- revisionToPublish = revision.id;
1075
+ // Scheduled sweep: atomically claim the row before any other write. A
1076
+ // single conditional UPDATE is atomic per-statement on every dialect
1077
+ // (it doesn't depend on a wrapping transaction, which D1 lacks). If the
1078
+ // schedule was cleared or pushed to the future (unschedule/reschedule)
1079
+ // or another sweep already claimed it, this affects 0 rows and we bail
1080
+ // before promoting any revision so the row can't be double-published.
1081
+ let claimedScheduledAt: string | null = null;
1082
+ let claimedUpdatedAt: string | null = null;
1083
+ if (requireDue) {
1084
+ const claim = await sql`
1085
+ UPDATE ${sql.ref(tableName)}
1086
+ SET scheduled_at = NULL,
1087
+ updated_at = ${now}
1088
+ WHERE id = ${id}
1089
+ AND scheduled_at IS NOT NULL
1090
+ AND scheduled_at <= ${now}
1091
+ AND deleted_at IS NULL
1092
+ `.execute(this.db);
1093
+ if ((claim.numAffectedRows ?? 0n) === 0n) {
1094
+ throw new ScheduledNotDueError();
1095
+ }
1096
+ // Remember what we cleared so we can put it back if the publish work
1097
+ // below fails on a driver without transactions (see catch). Both
1098
+ // values come from the pre-claim snapshot: if a concurrent
1099
+ // reschedule-to-a-different-past-time landed between findById and the
1100
+ // claim, the restore writes the snapshot value rather than the one the
1101
+ // claim actually cleared. That window is tiny and the restore is
1102
+ // best-effort retry bookkeeping, so the imprecision is acceptable.
1103
+ claimedScheduledAt = existing.scheduledAt;
1104
+ claimedUpdatedAt = existing.updatedAt;
1002
1105
  }
1003
1106
 
1004
- // Sync the revision's data into the content table columns
1005
- // so the content table always holds the published version
1006
- const revision = await revisionRepo.findById(revisionToPublish);
1007
- if (revision) {
1008
- await this.syncDataColumns(type, id, revision.data);
1107
+ // Track whether the final publish write committed. On D1 the claim above
1108
+ // is already durable (withTransaction is a no-op there), so if a later
1109
+ // step throws we must restore the schedule — otherwise the row is left
1110
+ // `scheduled` with `scheduled_at = NULL` and no sweep ever retries it.
1111
+ let publishCommitted = false;
1112
+ try {
1113
+ const revisionRepo = new RevisionRepository(this.db);
1114
+ let revisionToPublish = existing.draftRevisionId || existing.liveRevisionId;
1009
1115
 
1010
- // Sync slug from revision if stored there
1011
- if (typeof revision.data._slug === "string") {
1116
+ if (!revisionToPublish) {
1117
+ // No revision exists - create one from current data
1118
+ const revision = await revisionRepo.create({
1119
+ collection: type,
1120
+ entryId: id,
1121
+ data: existing.data,
1122
+ });
1123
+ revisionToPublish = revision.id;
1124
+ }
1125
+
1126
+ // Sync the revision's data into the content table columns
1127
+ // so the content table always holds the published version
1128
+ const revision = await revisionRepo.findById(revisionToPublish);
1129
+ if (revision) {
1130
+ await this.syncDataColumns(type, id, revision.data);
1131
+
1132
+ // Sync slug from revision if stored there
1133
+ if (typeof revision.data._slug === "string") {
1134
+ await sql`
1135
+ UPDATE ${sql.ref(tableName)}
1136
+ SET slug = ${revision.data._slug}
1137
+ WHERE id = ${id}
1138
+ `.execute(this.db);
1139
+ }
1140
+ }
1141
+
1142
+ if (publishedAt !== undefined) {
1143
+ // Caller supplied an explicit timestamp, so we overwrite published_at
1144
+ // directly (used to backdate a publish, e.g. for content migrations).
1145
+ await sql`
1146
+ UPDATE ${sql.ref(tableName)}
1147
+ SET live_revision_id = ${revisionToPublish},
1148
+ draft_revision_id = NULL,
1149
+ status = 'published',
1150
+ scheduled_at = NULL,
1151
+ published_at = ${publishedAt},
1152
+ updated_at = ${now}
1153
+ WHERE id = ${id}
1154
+ AND deleted_at IS NULL
1155
+ `.execute(this.db);
1156
+ } else {
1157
+ // No timestamp supplied — preserve existing published_at on
1158
+ // idempotent re-publish, fall back to `now` on first publish.
1012
1159
  await sql`
1013
1160
  UPDATE ${sql.ref(tableName)}
1014
- SET slug = ${revision.data._slug}
1161
+ SET live_revision_id = ${revisionToPublish},
1162
+ draft_revision_id = NULL,
1163
+ status = 'published',
1164
+ scheduled_at = NULL,
1165
+ published_at = COALESCE(published_at, ${now}),
1166
+ updated_at = ${now}
1015
1167
  WHERE id = ${id}
1168
+ AND deleted_at IS NULL
1016
1169
  `.execute(this.db);
1017
1170
  }
1018
- }
1171
+ publishCommitted = true;
1019
1172
 
1020
- if (publishedAt !== undefined) {
1021
- // Caller supplied an explicit timestamp, so we overwrite published_at
1022
- // directly (used to backdate a publish, e.g. for content migrations).
1023
- await sql`
1024
- UPDATE ${sql.ref(tableName)}
1025
- SET live_revision_id = ${revisionToPublish},
1026
- draft_revision_id = NULL,
1027
- status = 'published',
1028
- scheduled_at = NULL,
1029
- published_at = ${publishedAt},
1030
- updated_at = ${now}
1031
- WHERE id = ${id}
1032
- AND deleted_at IS NULL
1033
- `.execute(this.db);
1034
- } else {
1035
- // No timestamp supplied — preserve existing published_at on
1036
- // idempotent re-publish, fall back to `now` on first publish.
1037
- await sql`
1038
- UPDATE ${sql.ref(tableName)}
1039
- SET live_revision_id = ${revisionToPublish},
1040
- draft_revision_id = NULL,
1041
- status = 'published',
1042
- scheduled_at = NULL,
1043
- published_at = COALESCE(published_at, ${now}),
1044
- updated_at = ${now}
1045
- WHERE id = ${id}
1046
- AND deleted_at IS NULL
1047
- `.execute(this.db);
1048
- }
1173
+ const updated = await this.findById(type, id);
1174
+ if (!updated) {
1175
+ throw new Error("Content not found");
1176
+ }
1049
1177
 
1050
- const updated = await this.findById(type, id);
1051
- if (!updated) {
1052
- throw new Error("Content not found");
1178
+ return updated;
1179
+ } catch (error) {
1180
+ // Best-effort schedule restore for the no-transaction (D1) case so a
1181
+ // failed publish stays retryable. Skipped when the publish actually
1182
+ // committed (the failure was afterwards). On SQLite/Postgres the
1183
+ // enclosing transaction rolls the claim back, so this restore also
1184
+ // rolls back — a harmless no-op. Never mask the original error.
1185
+ if (requireDue && claimedScheduledAt && !publishCommitted) {
1186
+ try {
1187
+ // Only restore if the row still has pending work: either it's not
1188
+ // published, or it's a published row that still has a draft change
1189
+ // queued. This avoids re-adding a stale schedule (and triggering a
1190
+ // redundant republish) when another actor fully published the row
1191
+ // in the failure window — that publish clears draft_revision_id.
1192
+ // Restore updated_at to its pre-claim value too — the claim bumped
1193
+ // it to `now`, and a failed publish made no real change, so leaving
1194
+ // it advanced would be a phantom modification for "changed since"
1195
+ // consumers (sync, ETags, incremental indexers).
1196
+ await sql`
1197
+ UPDATE ${sql.ref(tableName)}
1198
+ SET scheduled_at = ${claimedScheduledAt},
1199
+ updated_at = ${claimedUpdatedAt ?? now}
1200
+ WHERE id = ${id}
1201
+ AND scheduled_at IS NULL
1202
+ AND deleted_at IS NULL
1203
+ AND (status != 'published' OR draft_revision_id IS NOT NULL)
1204
+ `.execute(this.db);
1205
+ } catch (restoreError) {
1206
+ console.error(
1207
+ `[content] Failed to restore schedule for ${type}/${id} after publish failure:`,
1208
+ restoreError,
1209
+ );
1210
+ }
1211
+ }
1212
+ throw error;
1053
1213
  }
1054
-
1055
- return updated;
1056
1214
  }
1057
1215
 
1058
1216
  /**
@@ -42,3 +42,10 @@ export { BylineRepository } from "./byline.js";
42
42
  export type { CreateBylineInput, UpdateBylineInput, ContentBylineInput } from "./byline.js";
43
43
  export type * from "./types.js";
44
44
  export { EmDashValidationError, InvalidCursorError, encodeCursor, decodeCursor } from "./types.js";
45
+ export { RelationRepository } from "./relation.js";
46
+ export type {
47
+ Relation,
48
+ CreateRelationInput,
49
+ UpdateRelationInput,
50
+ ContentReference,
51
+ } from "./relation.js";