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
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Responsive image helpers shared by the public Image components.
3
+ *
4
+ * These build a `srcset` for locally-stored / R2-stored media by delegating to
5
+ * Astro's configured image service (`astro:assets`). On Cloudflare that is the
6
+ * Images binding; on Node it is sharp; if neither is available it is a no-op
7
+ * passthrough. The calling `.astro` component passes Astro's `getImage` in so
8
+ * this module stays free of the `astro:assets` virtual import (which only
9
+ * resolves inside an Astro project, not in this precompiled package).
10
+ */
11
+
12
+ /** Standard responsive breakpoints. Matches CDN-provider srcset generation. */
13
+ export const RESPONSIVE_BREAKPOINTS = [640, 750, 828, 960, 1080, 1280, 1600, 1920];
14
+
15
+ /** Matches absolute http(s) URLs — the only shape Astro's image services optimize. */
16
+ const ABSOLUTE_HTTP_URL = /^https?:\/\//i;
17
+
18
+ /**
19
+ * Pick the srcset widths to generate for an image rendered at `maxWidth`.
20
+ * Includes breakpoints up to 2x (retina) plus the rendered width itself, so the
21
+ * browser always has an exact-fit candidate.
22
+ */
23
+ export function responsiveWidths(maxWidth: number): number[] {
24
+ const cap = maxWidth * 2;
25
+ const widths = new Set(RESPONSIVE_BREAKPOINTS.filter((w) => w <= cap));
26
+ widths.add(maxWidth);
27
+ return [...widths].toSorted((a, b) => a - b);
28
+ }
29
+
30
+ /** Build the `sizes` attribute for an image with a known display width. */
31
+ export function responsiveSizes(width: number | undefined): string {
32
+ return width ? `(min-width: ${width}px) ${width}px, 100vw` : "100vw";
33
+ }
34
+
35
+ /**
36
+ * Make a same-origin media URL absolute so Astro's image service can optimize it.
37
+ *
38
+ * Astro only optimizes absolute http(s) URLs; a same-origin proxy path like
39
+ * `/_emdash/api/media/file/x.jpg` is otherwise treated as an unoptimizable
40
+ * public asset. Resolving it against the site's public origin (and authorizing
41
+ * that origin via `image.remotePatterns`) lets the service transform it.
42
+ *
43
+ * Only **same-origin** root-relative paths are resolved. Protocol-relative
44
+ * URLs (`//evil.com/x`) and backslash tricks (`/\evil.com`) also start with `/`
45
+ * but resolve to a different origin -- a classic SSRF vector once a
46
+ * remotePattern authorizes the media path -- so anything that escapes the
47
+ * origin is returned unchanged (and then skipped by `buildResponsiveImage`,
48
+ * which only accepts absolute http(s) URLs). Already-absolute URLs (CDN/public
49
+ * bucket) and non-path values (`data:`, `blob:`) are returned unchanged too.
50
+ */
51
+ export function toAbsoluteMediaUrl(src: string, origin: string | undefined): string {
52
+ if (!src || !origin || !src.startsWith("/")) return src;
53
+ try {
54
+ const resolved = new URL(src, origin);
55
+ if (resolved.origin !== new URL(origin).origin) return src;
56
+ return resolved.href;
57
+ } catch {
58
+ return src;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Minimal structural subset of Astro's `getImage`. Astro's `ImageTransform`
64
+ * carries a `[key: string]: any` index signature, so the real `getImage` is
65
+ * assignable to this narrower type.
66
+ */
67
+ export type GetImage = (options: {
68
+ src: string;
69
+ width?: number;
70
+ height?: number;
71
+ widths?: number[];
72
+ sizes?: string;
73
+ }) => Promise<{ src: string; srcSet?: { attribute?: string } | undefined }>;
74
+
75
+ export interface ResponsiveImage {
76
+ src: string;
77
+ srcset?: string;
78
+ sizes?: string;
79
+ }
80
+
81
+ /**
82
+ * Generate a responsive `src`/`srcset`/`sizes` for a media URL via Astro's
83
+ * configured image service.
84
+ *
85
+ * Astro's image services (sharp, Cloudflare `/cdn-cgi/image`, and the default
86
+ * Cloudflare `cloudflare-binding` service) only optimize **absolute** URLs whose
87
+ * host is authorized via `image.domains` / `image.remotePatterns`. Anything else
88
+ * is passed through unchanged, which would yield a useless srcset (the same URL
89
+ * at every width descriptor). We therefore only attempt optimization for
90
+ * absolute http(s) URLs and verify the service actually rewrote the URL.
91
+ *
92
+ * Returns `null` so callers fall back to a plain `<img>` when:
93
+ * - dimensions are unknown (avoids an inferSize fetch on every render),
94
+ * - the URL is relative (a same-origin proxy/public asset Astro won't optimize),
95
+ * - the host isn't authorized (the service passed the URL through unchanged),
96
+ * - no image service is configured / `getImage` throws.
97
+ */
98
+ export async function buildResponsiveImage(
99
+ getImage: GetImage,
100
+ opts: { src: string; width?: number; height?: number },
101
+ ): Promise<ResponsiveImage | null> {
102
+ const { src, width, height } = opts;
103
+ if (!src || !width || !height) return null;
104
+ if (!ABSOLUTE_HTTP_URL.test(src)) return null;
105
+ try {
106
+ const sizes = responsiveSizes(width);
107
+ const result = await getImage({
108
+ src,
109
+ width,
110
+ height,
111
+ widths: responsiveWidths(width),
112
+ sizes,
113
+ });
114
+ // Passthrough: the service returned the source unchanged (unauthorized
115
+ // host or no optimization available). Don't emit a no-op srcset.
116
+ if (!result.src || result.src === src) return null;
117
+ return {
118
+ src: result.src,
119
+ srcset: result.srcSet?.attribute || undefined,
120
+ sizes,
121
+ };
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
@@ -34,8 +34,9 @@ export type RescheduleFn = () => void;
34
34
  /**
35
35
  * Executes overdue cron tasks.
36
36
  *
37
- * Called by platform-specific schedulers (NodeCronScheduler, EmDashScheduler DO,
38
- * PiggybackScheduler). Stateless all state lives in the database.
37
+ * Called by the platform driver: the NodeCronScheduler timer on Node, or the
38
+ * Worker's `scheduled()` handler (via runScheduledTasks) on Cloudflare.
39
+ * Stateless — all state lives in the database.
39
40
  */
40
41
  export class CronExecutor {
41
42
  constructor(
@@ -63,6 +63,11 @@ export type { RouteResult, InvokeRouteOptions } from "./routes.js";
63
63
  export { PluginManager, createPluginManager } from "./manager.js";
64
64
  export type { PluginManagerOptions, PluginState } from "./manager.js";
65
65
 
66
+ // Scheduler (Node timer-based heartbeat; consumed by the generated
67
+ // virtual:emdash/scheduler module on non-serverless adapters)
68
+ export { NodeCronScheduler } from "./scheduler/node.js";
69
+ export type { CronScheduler, SystemCleanupFn } from "./scheduler/types.js";
70
+
66
71
  // Sandbox
67
72
  export {
68
73
  NoopSandboxRunner,
@@ -8,8 +8,14 @@
8
8
  * - Marketplace ingest extends this with publishing-specific fields
9
9
  */
10
10
 
11
+ import {
12
+ capabilitiesToDeclaredAccess,
13
+ declaredAccessToCapabilities,
14
+ } from "@emdash-cms/plugin-types";
11
15
  import { z } from "zod";
12
16
 
17
+ import type { PluginManifest } from "./types.js";
18
+
13
19
  // ── Enum values (must stay in sync with types.ts) ───────────────
14
20
 
15
21
  /**
@@ -219,16 +225,63 @@ const pluginAdminConfigSchema = z.object({
219
225
  .optional(),
220
226
  });
221
227
 
228
+ // ── declaredAccess ──────────────────────────────────────────────
229
+
230
+ /**
231
+ * An operation's constraint object. Open vocabulary: keys the runtime
232
+ * recognises are enforced, others are advisory. The bundler emits `{}` for a
233
+ * granted operation; presence (not value) signals the grant.
234
+ */
235
+ const accessConstraints = z.record(z.string(), z.unknown());
236
+
237
+ /**
238
+ * Structured trust contract embedded in the bundle manifest. Mirrors
239
+ * `DeclaredAccess` in `@emdash-cms/plugin-types`. Categories are host
240
+ * subsystems; operations are modes of participation.
241
+ */
242
+ const declaredAccessSchema = z.object({
243
+ content: z
244
+ .object({ read: accessConstraints.optional(), write: accessConstraints.optional() })
245
+ .optional(),
246
+ media: z
247
+ .object({ read: accessConstraints.optional(), write: accessConstraints.optional() })
248
+ .optional(),
249
+ network: z
250
+ .object({
251
+ // allowedHosts: absent = unrestricted; present = host-restricted. Reject
252
+ // an empty array (which the decoder would otherwise have to treat as
253
+ // deny-all) to match the record lexicon's `minLength: 1` and keep the
254
+ // "absent vs empty" distinction from ever reaching enforcement ambiguous.
255
+ request: z.object({ allowedHosts: z.array(z.string()).min(1).optional() }).optional(),
256
+ })
257
+ .optional(),
258
+ email: z
259
+ .object({
260
+ send: accessConstraints.optional(),
261
+ events: accessConstraints.optional(),
262
+ transport: accessConstraints.optional(),
263
+ })
264
+ .optional(),
265
+ page: z.object({ fragments: accessConstraints.optional() }).optional(),
266
+ users: z.object({ read: accessConstraints.optional() }).optional(),
267
+ });
268
+
222
269
  // ── Main schema ─────────────────────────────────────────────────
223
270
 
224
271
  /**
225
272
  * Zod schema matching the PluginManifest interface from types.ts.
226
273
  *
227
274
  * Every JSON.parse of a manifest.json should validate through this.
275
+ *
276
+ * `declaredAccess` is the trust contract; `capabilities`/`allowedHosts` are the
277
+ * runtime's enforcement currency. Apply `reconcileManifestAccess` after parsing
278
+ * to make them consistent (declaredAccess authoritative when present). Kept a
279
+ * plain object (no `.transform`) because callers `.pick()`/`.extend()` it.
228
280
  */
229
281
  export const pluginManifestSchema = z.object({
230
282
  id: z.string().min(1),
231
283
  version: z.string().min(1),
284
+ declaredAccess: declaredAccessSchema.optional(),
232
285
  capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),
233
286
  allowedHosts: z.array(z.string()),
234
287
  storage: z.record(z.string(), storageCollectionSchema),
@@ -254,6 +307,28 @@ export const pluginManifestSchema = z.object({
254
307
 
255
308
  export type ValidatedPluginManifest = z.infer<typeof pluginManifestSchema>;
256
309
 
310
+ /**
311
+ * Reconcile a parsed manifest's trust contract with its enforcement currency.
312
+ * `declaredAccess` is authoritative: when present, `capabilities`/`allowedHosts`
313
+ * are re-derived from it so what the runtime enforces always matches what was
314
+ * recorded and consented to. A pre-migration bundle without `declaredAccess`
315
+ * has it derived from the legacy capability list instead. The result always
316
+ * carries both, mutually consistent. Apply this at every bundle-parse site.
317
+ */
318
+ export function reconcileManifestAccess(manifest: ValidatedPluginManifest): PluginManifest {
319
+ const reconciled: ValidatedPluginManifest = manifest.declaredAccess
320
+ ? { ...manifest, ...declaredAccessToCapabilities(manifest.declaredAccess) }
321
+ : {
322
+ ...manifest,
323
+ declaredAccess: capabilitiesToDeclaredAccess(manifest.capabilities, manifest.allowedHosts),
324
+ };
325
+ // Block Kit admin elements are typed as `unknown` by the Zod schema (their
326
+ // Element shape is validated at render time), so the validated manifest
327
+ // needs a structural cast up to the runtime PluginManifest.
328
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- admin elements are unknown[] in Zod; Element type checked at render time
329
+ return reconciled as unknown as PluginManifest;
330
+ }
331
+
257
332
  /**
258
333
  * Normalize a manifest hook entry — plain strings become `{ name }` objects.
259
334
  */
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { createGzipDecoder, unpackTar } from "modern-tar";
10
10
 
11
- import { pluginManifestSchema } from "./manifest-schema.js";
11
+ import { pluginManifestSchema, reconcileManifestAccess } from "./manifest-schema.js";
12
12
  import type { PluginManifest } from "./types.js";
13
13
 
14
14
  // ── Module-level regex patterns ───────────────────────────────────
@@ -495,10 +495,7 @@ export async function extractBundle(tarballBytes: Uint8Array): Promise<PluginBun
495
495
  "INVALID_BUNDLE",
496
496
  );
497
497
  }
498
- // Elements are validated as unknown[] by Zod; cast to PluginManifest
499
- // for the Element[] type (Block Kit validation happens at render time).
500
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time
501
- manifest = result.data as unknown as PluginManifest;
498
+ manifest = reconcileManifestAccess(result.data);
502
499
  } catch (err) {
503
500
  if (err instanceof MarketplaceError) throw err;
504
501
  throw new MarketplaceError(
@@ -15,8 +15,15 @@ import type { CronScheduler, SystemCleanupFn } from "./types.js";
15
15
  /** Minimum polling interval (ms) — prevents tight loops if next_run_at is in the past */
16
16
  const MIN_INTERVAL_MS = 1000;
17
17
 
18
- /** Maximum polling interval (ms) — wake up periodically to check for stale locks */
19
- const MAX_INTERVAL_MS = 5 * 60 * 1000;
18
+ /**
19
+ * Maximum polling interval (ms). Each wake runs the maintenance pass — stale
20
+ * lock recovery *and* the scheduled-publishing sweep + system cleanup. The cap
21
+ * is the worst-case latency for scheduled content when no plugin cron task is
22
+ * due sooner (`getNextDueTime()` only knows about cron tasks, not content
23
+ * `scheduled_at`). Held at 60s so Node publish latency matches the Cloudflare
24
+ * Cron Trigger cadence (`* * * * *`) rather than lagging up to five minutes.
25
+ */
26
+ const MAX_INTERVAL_MS = 60 * 1000;
20
27
 
21
28
  export class NodeCronScheduler implements CronScheduler {
22
29
  private timer: ReturnType<typeof setTimeout> | null = null;
@@ -19,10 +19,13 @@ import type { Element } from "@emdash-cms/blocks";
19
19
  // (e.g. `import { PluginCapability } from "../plugins/types.js"`).
20
20
  import {
21
21
  CAPABILITY_RENAMES,
22
+ capabilitiesToDeclaredAccess,
23
+ declaredAccessToCapabilities,
22
24
  isDeprecatedCapability,
23
25
  normalizeCapabilities,
24
26
  normalizeCapability,
25
27
  type CurrentPluginCapability,
28
+ type DeclaredAccess,
26
29
  type DeprecatedPluginCapability,
27
30
  type ManifestHookEntry,
28
31
  type ManifestRouteEntry,
@@ -40,10 +43,13 @@ import type { FieldType } from "../schema/types.js";
40
43
 
41
44
  export {
42
45
  CAPABILITY_RENAMES,
46
+ capabilitiesToDeclaredAccess,
47
+ declaredAccessToCapabilities,
43
48
  isDeprecatedCapability,
44
49
  normalizeCapabilities,
45
50
  normalizeCapability,
46
51
  type CurrentPluginCapability,
52
+ type DeclaredAccess,
47
53
  type DeprecatedPluginCapability,
48
54
  type ManifestHookEntry,
49
55
  type ManifestRouteEntry,
@@ -1336,6 +1342,12 @@ export interface PluginAdminExports {
1336
1342
  export interface PluginManifest {
1337
1343
  id: string;
1338
1344
  version: string;
1345
+ /**
1346
+ * The trust contract (see `@emdash-cms/plugin-types`). Authoritative;
1347
+ * `capabilities`/`allowedHosts` are derived from it at the parse boundary
1348
+ * via `reconcileManifestAccess`. Optional during the wire-format migration.
1349
+ */
1350
+ declaredAccess?: DeclaredAccess;
1339
1351
  capabilities: PluginCapability[];
1340
1352
  allowedHosts: string[];
1341
1353
  storage: PluginStorageConfig;
package/src/query.ts CHANGED
@@ -101,13 +101,19 @@ export interface CollectionFilter {
101
101
  */
102
102
  cursor?: string;
103
103
  /**
104
- * Filter by field values, taxonomy terms, or ranges.
104
+ * Filter by field values, taxonomy terms, byline credits, or ranges.
105
105
  *
106
106
  * Taxonomy names are detected automatically and filtered via JOIN.
107
+ * The reserved `byline` key filters by byline credit (any credit, not
108
+ * just the primary one) via the `_emdash_content_bylines` junction
109
+ * table; its value is one or more byline translation groups. This
110
+ * matches co-authored entries, which `primary_byline_id` alone misses.
107
111
  * Other keys are treated as column filters on the content table.
108
112
  *
109
113
  * @example { category: 'news' } - Filter by taxonomy term
110
114
  * @example { category: ['news', 'featured'] } - Filter by multiple terms (OR)
115
+ * @example { byline: '01HXYZ...' } - Entries credited to a byline (any position)
116
+ * @example { byline: ['01HXYZ...', '01HABC...'] } - Credited to any of these bylines (OR)
111
117
  * @example { series: 'main' } - Exact match on a content field
112
118
  * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - Date range
113
119
  */
@@ -241,6 +247,17 @@ function dataStr(data: Record<string, unknown>, key: string, fallback = ""): str
241
247
  return typeof val === "string" ? val : fallback;
242
248
  }
243
249
 
250
+ /** Safely read a date-like field from a Record */
251
+ function dataDate(data: Record<string, unknown>, key: string): Date | undefined {
252
+ const val = data[key];
253
+ if (val instanceof Date) {
254
+ return Number.isNaN(val.getTime()) ? undefined : val;
255
+ }
256
+ if (typeof val !== "string" && typeof val !== "number") return undefined;
257
+ const date = new Date(val);
258
+ return Number.isNaN(date.getTime()) ? undefined : date;
259
+ }
260
+
244
261
  /** Type guard for Record<string, unknown> */
245
262
  function isRecord(value: unknown): value is Record<string, unknown> {
246
263
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -533,7 +550,9 @@ async function getEmDashCollectionUncached<T extends string, D = InferCollection
533
550
  // round-trip cost on remote databases (D1 replicas, etc.).
534
551
  await Promise.all([
535
552
  hydrateEntryBylines(type, entriesWithEdit),
536
- hydrateEntryTerms(type, entriesWithEdit),
553
+ // Hydrate terms in the same locale the content rows were resolved to,
554
+ // otherwise localized entries get default-locale taxonomy terms (#1441).
555
+ hydrateEntryTerms(type, entriesWithEdit, resolvedLocale),
537
556
  ]);
538
557
 
539
558
  return { entries: entriesWithEdit, nextCursor, cacheHint: cacheHint ?? {} };
@@ -594,10 +613,10 @@ export async function getEmDashEntry<T extends string, D = InferCollectionData<T
594
613
  function isVisible(entry: ContentEntry<D>): boolean {
595
614
  const data = entryData(entry);
596
615
  const status = dataStr(data, "status");
597
- const scheduledAt = dataStr(data, "scheduledAt") || undefined;
616
+ const scheduledAt = dataDate(data, "scheduledAt");
598
617
  const isPublished = status === "published";
599
618
  const isScheduledAndReady =
600
- status === "scheduled" && scheduledAt && new Date(scheduledAt) <= new Date();
619
+ status === "scheduled" && scheduledAt !== undefined && scheduledAt.getTime() <= Date.now();
601
620
  return isPublished || !!isScheduledAndReady;
602
621
  }
603
622
 
@@ -611,7 +630,17 @@ export async function getEmDashEntry<T extends string, D = InferCollectionData<T
611
630
  wrapped: ContentEntry<D>,
612
631
  opts: { isPreview: boolean; fallbackLocale?: string; cacheHint: CacheHint },
613
632
  ): Promise<EntryResult<D>> {
614
- await Promise.all([hydrateEntryBylines(type, [wrapped]), hydrateEntryTerms(type, [wrapped])]);
633
+ // Hydrate terms in the entry's resolved locale (fallback-aware) so a
634
+ // localized entry never picks up default-locale taxonomy terms (#1441).
635
+ // When i18n is disabled we leave the locale unset to preserve the
636
+ // legacy "do not filter by locale" behaviour.
637
+ const termLocale = isI18nEnabled()
638
+ ? dataStr(entryData(wrapped), "locale") || undefined
639
+ : undefined;
640
+ await Promise.all([
641
+ hydrateEntryBylines(type, [wrapped]),
642
+ hydrateEntryTerms(type, [wrapped], termLocale),
643
+ ]);
615
644
  return {
616
645
  entry: wrapped,
617
646
  isPreview: opts.isPreview,
@@ -788,9 +817,18 @@ async function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]):
788
817
  * results and call getEntryTerms() per entry. With hydration, the list page
789
818
  * stays at a single round-trip for term data.
790
819
  *
820
+ * `locale` must be the locale the entries were resolved to. It is forwarded to
821
+ * `getAllTermsForEntries` so terms are returned in the entry's locale rather
822
+ * than falling back to the request-context / default locale (#1441). Pass
823
+ * `undefined` to keep the legacy "do not filter by locale" behaviour.
824
+ *
791
825
  * Fails silently if the taxonomy tables don't exist yet (pre-migration).
792
826
  */
793
- async function hydrateEntryTerms<D>(type: string, entries: ContentEntry<D>[]): Promise<void> {
827
+ async function hydrateEntryTerms<D>(
828
+ type: string,
829
+ entries: ContentEntry<D>[],
830
+ locale?: string,
831
+ ): Promise<void> {
794
832
  if (entries.length === 0) return;
795
833
 
796
834
  try {
@@ -799,7 +837,7 @@ async function hydrateEntryTerms<D>(type: string, entries: ContentEntry<D>[]): P
799
837
  const ids = entries.map((e) => dataStr(entryData(e), "id")).filter(Boolean);
800
838
  if (ids.length === 0) return;
801
839
 
802
- const termsMap = await getAllTermsForEntries(type, ids);
840
+ const termsMap = await getAllTermsForEntries(type, ids, { locale });
803
841
 
804
842
  for (const entry of entries) {
805
843
  const data = entryData(entry);
@@ -39,6 +39,13 @@ export interface RequestMetrics {
39
39
  dbLastOffset: number | null;
40
40
  cacheHits: number;
41
41
  cacheMisses: number;
42
+ /**
43
+ * Physical database round trips. Differs from `dbCount` (logical queries)
44
+ * when a backend batches: the DO SQL driver coalesces same-turn SELECTs into
45
+ * one RPC, so `rpcCount` can be far lower than `dbCount`. Bumped by the
46
+ * adapter, not the Kysely log hook.
47
+ */
48
+ rpcCount: number;
42
49
  }
43
50
 
44
51
  export function createRequestMetrics(start: number): RequestMetrics {
@@ -50,6 +57,7 @@ export function createRequestMetrics(start: number): RequestMetrics {
50
57
  dbLastOffset: null,
51
58
  cacheHits: 0,
52
59
  cacheMisses: 0,
60
+ rpcCount: 0,
53
61
  };
54
62
  }
55
63
 
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Scheduled publishing sweep
3
+ *
4
+ * Promotes content whose scheduled publish time has passed. Driven by the
5
+ * platform scheduler alongside cron ticks and system cleanup — never by a
6
+ * request. On Node the cron scheduler's maintenance pass calls it; on
7
+ * Cloudflare the Worker's `scheduled()` handler does.
8
+ *
9
+ * Like `runSystemCleanup`, each collection sweep is independent and non-fatal:
10
+ * one collection failing must not stop the rest.
11
+ */
12
+
13
+ import type { Kysely } from "kysely";
14
+
15
+ import { handleContentPublish } from "./api/handlers/content.js";
16
+ import { ContentRepository } from "./database/repositories/content.js";
17
+ import type { Database } from "./database/types.js";
18
+ import { SchemaRegistry } from "./schema/registry.js";
19
+
20
+ /** A content item that was promoted to published by a sweep. */
21
+ export interface PublishedRef {
22
+ collection: string;
23
+ id: string;
24
+ }
25
+
26
+ /**
27
+ * Default cap on items promoted per collection in a single sweep. Bounds the
28
+ * publish/webhook fan-out of one tick so a large backlog can't exhaust a Worker
29
+ * invocation's CPU/subrequest budget; the remainder drains on later ticks.
30
+ */
31
+ export const SCHEDULED_PUBLISH_BATCH_LIMIT = 100;
32
+
33
+ /**
34
+ * Publishes a single content item. Mirrors the relevant subset of
35
+ * `handleContentPublish`'s return shape. Production callers pass
36
+ * `EmDashRuntime.handleContentPublish` so `content:afterPublish` hooks fire
37
+ * (search indexing, webhooks, syndication); the default falls back to the raw
38
+ * handler (no hooks) for callers that have only a `db`.
39
+ */
40
+ export type ScheduledPublishFn = (
41
+ collection: string,
42
+ id: string,
43
+ options: { publishedAt?: string; requireScheduledDue?: boolean },
44
+ ) => Promise<{ success: boolean; error?: { code?: string } }>;
45
+
46
+ export interface PublishDueContentOptions {
47
+ /**
48
+ * Publish callback. Production callers pass the runtime's
49
+ * `handleContentPublish` so `content:afterPublish` hooks fire (search
50
+ * indexing, webhooks, syndication). Defaults to the raw DB handler (no hooks).
51
+ */
52
+ publish?: ScheduledPublishFn;
53
+ /**
54
+ * Invoked after each collection's batch with the items promoted in that
55
+ * batch. Lets request-less callers (the Cloudflare `scheduled()` handler)
56
+ * purge edge-cache tags incrementally instead of only after the whole sweep,
57
+ * so a runtime killed mid-sweep strands at most one batch behind stale cache
58
+ * rather than everything published so far. Failures are logged, never fatal.
59
+ */
60
+ onPublished?: (refs: PublishedRef[]) => Promise<void>;
61
+ /**
62
+ * Maximum items promoted per collection per sweep. Defaults to
63
+ * `SCHEDULED_PUBLISH_BATCH_LIMIT`. Pass `0` (or a negative) for unbounded.
64
+ */
65
+ limit?: number;
66
+ }
67
+
68
+ /**
69
+ * Publish every content item whose `scheduled_at` is in the past.
70
+ *
71
+ * Iterates all collections, finds due items (`findReadyToPublish` returns both
72
+ * scheduled drafts and published entries with pending scheduled changes), and
73
+ * publishes each. `publish()` clears `scheduled_at`, so a second sweep is a
74
+ * no-op — safe to run on every tick.
75
+ *
76
+ * Bounded per collection by `limit` (default `SCHEDULED_PUBLISH_BATCH_LIMIT`):
77
+ * a large backlog drains across successive ticks rather than in one unbounded
78
+ * pass. After each collection's batch, `onPublished` (if given) is awaited so
79
+ * cache-tag invalidation happens incrementally, not just at the very end.
80
+ *
81
+ * Returns every item it promoted so request-less callers (the Cloudflare
82
+ * `scheduled()` handler) can also act on the full set.
83
+ */
84
+ export async function publishDueContent(
85
+ db: Kysely<Database>,
86
+ options: PublishDueContentOptions = {},
87
+ ): Promise<PublishedRef[]> {
88
+ const { publish, onPublished, limit = SCHEDULED_PUBLISH_BATCH_LIMIT } = options;
89
+ const published: PublishedRef[] = [];
90
+
91
+ let collections;
92
+ try {
93
+ collections = await new SchemaRegistry(db).listCollections();
94
+ } catch (error) {
95
+ console.error("[scheduled-publish] Failed to list collections:", error);
96
+ return published;
97
+ }
98
+
99
+ const repo = new ContentRepository(db);
100
+ const doPublish: ScheduledPublishFn =
101
+ publish ?? ((collection, id, opts) => handleContentPublish(db, collection, id, opts));
102
+ // 0 / negative means unbounded; findReadyToPublish treats that as "no LIMIT".
103
+ const batchLimit = limit > 0 ? limit : undefined;
104
+
105
+ for (const collection of collections) {
106
+ try {
107
+ const due = await repo.findReadyToPublish(collection.slug, batchLimit);
108
+ const batch: PublishedRef[] = [];
109
+ for (const item of due) {
110
+ // First publication of a scheduled draft should record the intended
111
+ // scheduled time, not the (later) sweep time. Items already published
112
+ // with pending draft changes keep their original published_at.
113
+ const publishedAt = item.publishedAt == null ? (item.scheduledAt ?? undefined) : undefined;
114
+ const result = await doPublish(collection.slug, item.id, {
115
+ publishedAt,
116
+ requireScheduledDue: true,
117
+ });
118
+ if (result.success) {
119
+ batch.push({ collection: collection.slug, id: item.id });
120
+ } else if (result.error?.code === "NOT_DUE") {
121
+ // Unscheduled or rescheduled between selection and publish — the
122
+ // editor changed their mind; skip quietly, not a failure.
123
+ } else {
124
+ console.error(
125
+ `[scheduled-publish] Failed to publish ${collection.slug}/${item.id}:`,
126
+ result.error,
127
+ );
128
+ }
129
+ }
130
+
131
+ if (batch.length > 0) {
132
+ published.push(...batch);
133
+ if (onPublished) {
134
+ // Purge this batch's cache tags before moving to the next
135
+ // collection, so a mid-sweep kill can't strand already-published
136
+ // content behind stale cache.
137
+ try {
138
+ await onPublished(batch);
139
+ } catch (error) {
140
+ console.error(
141
+ `[scheduled-publish] onPublished failed after "${collection.slug}" batch:`,
142
+ error,
143
+ );
144
+ }
145
+ }
146
+ }
147
+ } catch (error) {
148
+ console.error(`[scheduled-publish] Sweep failed for "${collection.slug}":`, error);
149
+ }
150
+ }
151
+
152
+ return published;
153
+ }
@@ -102,7 +102,16 @@ export type CollectionSource =
102
102
  /** Sub-field definition for repeater fields */
103
103
  export interface RepeaterSubField {
104
104
  slug: string;
105
- type: "string" | "text" | "url" | "number" | "integer" | "boolean" | "datetime" | "select";
105
+ type:
106
+ | "string"
107
+ | "text"
108
+ | "url"
109
+ | "number"
110
+ | "integer"
111
+ | "boolean"
112
+ | "datetime"
113
+ | "select"
114
+ | "image";
106
115
  label: string;
107
116
  required?: boolean;
108
117
  options?: string[]; // For select sub-fields
@@ -118,6 +127,7 @@ export const REPEATER_SUB_FIELD_TYPES = [
118
127
  "boolean",
119
128
  "datetime",
120
129
  "select",
130
+ "image",
121
131
  ] as const;
122
132
 
123
133
  export interface FieldValidation {