emdash 0.18.0 → 0.19.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 (408) hide show
  1. package/dist/api/route-utils.d.mts +2 -2
  2. package/dist/api/route-utils.mjs +14 -14
  3. package/dist/api/schemas/index.d.mts +2 -2
  4. package/dist/api/schemas/index.mjs +3 -3
  5. package/dist/{api-Cs7DAACP.mjs → api-BZ6bhjYs.mjs} +88 -16
  6. package/dist/api-BZ6bhjYs.mjs.map +1 -0
  7. package/dist/{apply-BWMV4Zmw.mjs → apply-hQkKKBCf.mjs} +23 -23
  8. package/dist/apply-hQkKKBCf.mjs.map +1 -0
  9. package/dist/astro/index.d.mts +8 -8
  10. package/dist/astro/index.d.mts.map +1 -1
  11. package/dist/astro/index.mjs +113 -23
  12. package/dist/astro/index.mjs.map +1 -1
  13. package/dist/astro/middleware/auth.d.mts +7 -7
  14. package/dist/astro/middleware/auth.mjs +2 -2
  15. package/dist/astro/middleware/redirect.mjs +4 -4
  16. package/dist/astro/middleware/request-context.mjs +2 -2
  17. package/dist/astro/middleware.d.mts +26 -4
  18. package/dist/astro/middleware.d.mts.map +1 -1
  19. package/dist/astro/middleware.mjs +205 -173
  20. package/dist/astro/middleware.mjs.map +1 -1
  21. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  22. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  23. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  24. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  25. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
  26. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  27. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  28. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  29. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  30. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  31. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  32. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  33. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  34. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  35. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  36. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  37. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  38. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  39. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  40. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  41. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +31 -31
  42. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +31 -31
  43. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +30 -30
  44. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +30 -30
  45. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +30 -30
  46. package/dist/astro/routes/api/admin/plugins/index.mjs +30 -30
  47. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +30 -30
  49. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +30 -30
  50. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +30 -30
  51. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +30 -30
  52. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +31 -31
  53. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +30 -30
  54. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +31 -31
  55. package/dist/astro/routes/api/admin/plugins/updates.mjs +30 -30
  56. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +30 -30
  57. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  58. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +30 -30
  59. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  60. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  61. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  62. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  63. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  64. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  65. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  66. package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
  67. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  68. package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
  69. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  70. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  71. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  72. package/dist/astro/routes/api/auth/me.mjs +6 -6
  73. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +2 -2
  74. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  75. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  76. package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
  77. package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
  78. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
  79. package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
  80. package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
  81. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  82. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  83. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  84. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  85. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
  86. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  87. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  88. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  89. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
  90. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
  91. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  92. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  93. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  95. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
  96. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  97. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  98. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  99. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
  100. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  101. package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
  102. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  103. package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
  104. package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
  105. package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
  106. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
  107. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  108. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  109. package/dist/astro/routes/api/dashboard.mjs +7 -7
  110. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  111. package/dist/astro/routes/api/import/probe.d.mts +2 -2
  112. package/dist/astro/routes/api/import/probe.mjs +6 -6
  113. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  114. package/dist/astro/routes/api/import/wordpress/execute.d.mts +7 -7
  115. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  116. package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
  117. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  118. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  119. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  120. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  121. package/dist/astro/routes/api/manifest.mjs +3 -3
  122. package/dist/astro/routes/api/mcp.mjs +28 -28
  123. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  124. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  125. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  126. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  127. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  128. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  129. package/dist/astro/routes/api/media/upload-url.mjs +6 -6
  130. package/dist/astro/routes/api/media.mjs +7 -7
  131. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  132. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  133. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  134. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  135. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  136. package/dist/astro/routes/api/menus/index.mjs +7 -7
  137. package/dist/astro/routes/api/oauth/authorize.mjs +1 -1
  138. package/dist/astro/routes/api/oauth/device/authorize.mjs +4 -4
  139. package/dist/astro/routes/api/oauth/device/code.mjs +5 -5
  140. package/dist/astro/routes/api/oauth/device/token.mjs +5 -5
  141. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  142. package/dist/astro/routes/api/oauth/token/refresh.mjs +4 -4
  143. package/dist/astro/routes/api/oauth/token/revoke.mjs +4 -4
  144. package/dist/astro/routes/api/oauth/token.mjs +4 -4
  145. package/dist/astro/routes/api/openapi.json.mjs +17 -3
  146. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  147. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  148. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  149. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  150. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  151. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  152. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  153. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  154. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +30 -30
  155. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +30 -30
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +30 -30
  157. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +30 -30
  158. package/dist/astro/routes/api/schema/collections/index.mjs +30 -30
  159. package/dist/astro/routes/api/schema/index.mjs +6 -6
  160. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +30 -30
  161. package/dist/astro/routes/api/schema/orphans/index.mjs +30 -30
  162. package/dist/astro/routes/api/search/enable.mjs +9 -9
  163. package/dist/astro/routes/api/search/index.mjs +8 -8
  164. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  165. package/dist/astro/routes/api/search/stats.mjs +6 -6
  166. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  167. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  168. package/dist/astro/routes/api/sections/index.mjs +8 -8
  169. package/dist/astro/routes/api/settings/email.mjs +5 -5
  170. package/dist/astro/routes/api/settings.mjs +12 -12
  171. package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
  172. package/dist/astro/routes/api/setup/admin.mjs +6 -6
  173. package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
  174. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  175. package/dist/astro/routes/api/setup/index.mjs +21 -21
  176. package/dist/astro/routes/api/setup/status.mjs +3 -3
  177. package/dist/astro/routes/api/snapshot.mjs +5 -5
  178. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  179. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  180. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  181. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  182. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  183. package/dist/astro/routes/api/typegen.mjs +5 -5
  184. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  185. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  186. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
  187. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
  188. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  189. package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
  190. package/dist/astro/routes/api/widget-components.mjs +2 -2
  191. package/dist/astro/routes/robots.txt.mjs +6 -6
  192. package/dist/astro/routes/sitemap-_collection_.xml.mjs +6 -6
  193. package/dist/astro/routes/sitemap.xml.mjs +6 -6
  194. package/dist/astro/types.d.mts +15 -8
  195. package/dist/astro/types.d.mts.map +1 -1
  196. package/dist/{authorize-CotM4Yiu.mjs → authorize-C_8t2KGa.mjs} +2 -2
  197. package/dist/{authorize-CotM4Yiu.mjs.map → authorize-C_8t2KGa.mjs.map} +1 -1
  198. package/dist/{byline-CWQ9aSoz.mjs → byline-DUx48sJp.mjs} +6 -6
  199. package/dist/{byline-CWQ9aSoz.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
  200. package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-51kg6Vuv.mjs} +3 -3
  201. package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-51kg6Vuv.mjs.map} +1 -1
  202. package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields-C_OsR-KF.mjs} +2 -2
  203. package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields-C_OsR-KF.mjs.map} +1 -1
  204. package/dist/{byline-fields-BNy7Ng1U.d.mts → byline-fields-DYXKDuNX.d.mts} +26 -2
  205. package/dist/byline-fields-DYXKDuNX.d.mts.map +1 -0
  206. package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
  207. package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
  208. package/dist/{bylines-LJMgENMI.mjs → bylines-Cx5n-WqP.mjs} +3 -3
  209. package/dist/{bylines-LJMgENMI.mjs.map → bylines-Cx5n-WqP.mjs.map} +1 -1
  210. package/dist/{bylines-BJSva1Un.mjs → bylines-wurS258E.mjs} +50 -6
  211. package/dist/{bylines-BJSva1Un.mjs.map → bylines-wurS258E.mjs.map} +1 -1
  212. package/dist/{cache-lZL7SgVb.mjs → cache-B_HzASVT.mjs} +3 -3
  213. package/dist/{cache-lZL7SgVb.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
  214. package/dist/{chunks-BU-vP9Dh.mjs → chunks-BerYVuve.mjs} +2 -2
  215. package/dist/{chunks-BU-vP9Dh.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
  216. package/dist/cli/index.mjs +40 -27
  217. package/dist/cli/index.mjs.map +1 -1
  218. package/dist/client/cf-access.d.mts +1 -1
  219. package/dist/client/index.d.mts +1 -1
  220. package/dist/{comment-C4jVbCM8.mjs → comment-sqQxNpN3.mjs} +2 -2
  221. package/dist/{comment-C4jVbCM8.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
  222. package/dist/{comments-BTAbC0Ek.mjs → comments-CJ0RZsYR.mjs} +3 -3
  223. package/dist/{comments-BTAbC0Ek.mjs.map → comments-CJ0RZsYR.mjs.map} +1 -1
  224. package/dist/{content-CyqOmOzm.mjs → content-BIlVx-RX.mjs} +132 -43
  225. package/dist/content-BIlVx-RX.mjs.map +1 -0
  226. package/dist/{context-DZ7bEh5-.mjs → context-GG52SPgh.mjs} +10 -10
  227. package/dist/{context-DZ7bEh5-.mjs.map → context-GG52SPgh.mjs.map} +1 -1
  228. package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
  229. package/dist/cron-BJ2ClIlj.mjs.map +1 -0
  230. package/dist/{dashboard-B5WQpNTP.mjs → dashboard-2JgAMWxK.mjs} +4 -4
  231. package/dist/{dashboard-B5WQpNTP.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
  232. package/dist/db/index.d.mts +2 -2
  233. package/dist/db/index.mjs +1 -1
  234. package/dist/{device-flow-ptLrVINd.mjs → device-flow-s6_q3T7A.mjs} +2 -2
  235. package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-s6_q3T7A.mjs.map} +1 -1
  236. package/dist/{error-DJOsMVSt.mjs → error-RwM4dD35.mjs} +2 -2
  237. package/dist/{error-DJOsMVSt.mjs.map → error-RwM4dD35.mjs.map} +1 -1
  238. package/dist/{fts-manager-DR1ERA0c.mjs → fts-manager-1RgHmopc.mjs} +2 -2
  239. package/dist/{fts-manager-DR1ERA0c.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
  240. package/dist/{index-D60_SzHG.d.mts → index-BpYeJO1E.d.mts} +2 -2
  241. package/dist/{index-D60_SzHG.d.mts.map → index-BpYeJO1E.d.mts.map} +1 -1
  242. package/dist/{index-CjKdMZ3U.d.mts → index-FfiTQJq2.d.mts} +199 -17
  243. package/dist/index-FfiTQJq2.d.mts.map +1 -0
  244. package/dist/index.d.mts +9 -9
  245. package/dist/index.mjs +43 -43
  246. package/dist/{load-6ZrRhepW.mjs → load-B84ohfBk.mjs} +2 -2
  247. package/dist/{load-6ZrRhepW.mjs.map → load-B84ohfBk.mjs.map} +1 -1
  248. package/dist/{loader-Dyx8dhFV.mjs → loader-CpZKpFz0.mjs} +32 -30
  249. package/dist/loader-CpZKpFz0.mjs.map +1 -0
  250. package/dist/media/index.mjs +1 -1
  251. package/dist/media/local-runtime.d.mts +7 -7
  252. package/dist/media/local-runtime.mjs +6 -6
  253. package/dist/{media-C-oovGCG.mjs → media-JOf3pNkw.mjs} +2 -2
  254. package/dist/{media-C-oovGCG.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
  255. package/dist/{menus-DugoYwTX.mjs → menus-DX4_E01q.mjs} +3 -3
  256. package/dist/{menus-DugoYwTX.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
  257. package/dist/{menus-BKkxXCmd.mjs → menus-Dp9xporj.mjs} +86 -9
  258. package/dist/menus-Dp9xporj.mjs.map +1 -0
  259. package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
  260. package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
  261. package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-1aPAYjiC.mjs} +2 -2
  262. package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-1aPAYjiC.mjs.map} +1 -1
  263. package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
  264. package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
  265. package/dist/{options-tb7DJROi.d.mts → options-D4MnavW_.d.mts} +3 -3
  266. package/dist/{options-tb7DJROi.d.mts.map → options-D4MnavW_.d.mts.map} +1 -1
  267. package/dist/{parse-BBkFmLVr.mjs → parse-CrGndy1A.mjs} +2 -2
  268. package/dist/{parse-BBkFmLVr.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
  269. package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
  270. package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
  271. package/dist/plugin-utils.d.mts +7 -7
  272. package/dist/plugins/adapt-sandbox-entry.d.mts +7 -7
  273. package/dist/{query-Ctlq1aOk.mjs → query-BFQ029Ts.mjs} +21 -15
  274. package/dist/query-BFQ029Ts.mjs.map +1 -0
  275. package/dist/{rate-limit-CH6W6ikK.mjs → rate-limit-ClFFUga6.mjs} +2 -2
  276. package/dist/{rate-limit-CH6W6ikK.mjs.map → rate-limit-ClFFUga6.mjs.map} +1 -1
  277. package/dist/{redirect-C6tJA7tk.mjs → redirect-CRWIt8Zj.mjs} +3 -3
  278. package/dist/{redirect-C6tJA7tk.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
  279. package/dist/{redirects-C0L9JUk4.mjs → redirects-DEygMrRO.mjs} +25 -3
  280. package/dist/redirects-DEygMrRO.mjs.map +1 -0
  281. package/dist/{redirects-CacE9eQa.mjs → redirects-OIu6vQ2i.mjs} +5 -5
  282. package/dist/{redirects-CacE9eQa.mjs.map → redirects-OIu6vQ2i.mjs.map} +1 -1
  283. package/dist/{registry-CIDxZbhh.mjs → registry-brYh-rAT.mjs} +6 -6
  284. package/dist/{registry-CIDxZbhh.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
  285. package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
  286. package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
  287. package/dist/{runner-pt6Wl-l-.mjs → runner--4wMWwKM.mjs} +217 -166
  288. package/dist/runner--4wMWwKM.mjs.map +1 -0
  289. package/dist/{runner-DM1yR5qd.d.mts → runner-BcRuXq_h.d.mts} +2 -2
  290. package/dist/{runner-DM1yR5qd.d.mts.map → runner-BcRuXq_h.d.mts.map} +1 -1
  291. package/dist/runtime.d.mts +7 -7
  292. package/dist/runtime.mjs +2 -2
  293. package/dist/{schema-B4tk0HAG.mjs → schema-CS7Eg5gh.mjs} +5 -5
  294. package/dist/{schema-B4tk0HAG.mjs.map → schema-CS7Eg5gh.mjs.map} +1 -1
  295. package/dist/{search-f-fNfwab.mjs → search-o-aQzHI1.mjs} +4 -4
  296. package/dist/{search-f-fNfwab.mjs.map → search-o-aQzHI1.mjs.map} +1 -1
  297. package/dist/{secrets-YYbTgB1w.mjs → secrets-C_ZtRos3.mjs} +2 -2
  298. package/dist/{secrets-YYbTgB1w.mjs.map → secrets-C_ZtRos3.mjs.map} +1 -1
  299. package/dist/{sections-biElLfT9.mjs → sections-DhsZ0ns9.mjs} +3 -3
  300. package/dist/{sections-biElLfT9.mjs.map → sections-DhsZ0ns9.mjs.map} +1 -1
  301. package/dist/seed/index.d.mts +2 -2
  302. package/dist/seed/index.mjs +16 -16
  303. package/dist/seo/index.d.mts +1 -1
  304. package/dist/{seo-BR39kvTF.mjs → seo-B5e6y9Wk.mjs} +2 -2
  305. package/dist/{seo-BR39kvTF.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
  306. package/dist/{service-BhR2acnc.mjs → service-DAxg8RPR.mjs} +2 -2
  307. package/dist/{service-BhR2acnc.mjs.map → service-DAxg8RPR.mjs.map} +1 -1
  308. package/dist/{settings-b5zW1R1T.mjs → settings-B1p-gPUK.mjs} +5 -5
  309. package/dist/{settings-b5zW1R1T.mjs.map → settings-B1p-gPUK.mjs.map} +1 -1
  310. package/dist/{settings-D_NJvjgN.mjs → settings-DIsbHTRE.mjs} +3 -3
  311. package/dist/{settings-D_NJvjgN.mjs.map → settings-DIsbHTRE.mjs.map} +1 -1
  312. package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-Yuv78yua.mjs} +2 -2
  313. package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-Yuv78yua.mjs.map} +1 -1
  314. package/dist/{site-url-Cm8-sJy7.mjs → site-url-mEVmwIFi.mjs} +2 -2
  315. package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-mEVmwIFi.mjs.map} +1 -1
  316. package/dist/{taxonomies-Crtzy4MT.mjs → taxonomies-BEW7S5AI.mjs} +7 -6
  317. package/dist/taxonomies-BEW7S5AI.mjs.map +1 -0
  318. package/dist/{taxonomies-Mhn9rjTQ.mjs → taxonomies-UusDXv3C.mjs} +4 -4
  319. package/dist/{taxonomies-Mhn9rjTQ.mjs.map → taxonomies-UusDXv3C.mjs.map} +1 -1
  320. package/dist/{taxonomy-DTZrIQpi.mjs → taxonomy-CdllE4oq.mjs} +3 -3
  321. package/dist/{taxonomy-DTZrIQpi.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
  322. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
  323. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
  324. package/dist/{transport-OnMNbsIA.d.mts → transport-BwQeeY2p.d.mts} +1 -1
  325. package/dist/{transport-OnMNbsIA.d.mts.map → transport-BwQeeY2p.d.mts.map} +1 -1
  326. package/dist/{types-K3MDsxpy.mjs → types-BXSUSAjt.mjs} +16 -3
  327. package/dist/{types-K3MDsxpy.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
  328. package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
  329. package/dist/{types-D8bhH891.mjs.map → types-DZk_y-MU.mjs.map} +1 -1
  330. package/dist/{types-DawhLFwy.d.mts → types-OT_Es5mp.d.mts} +26 -1
  331. package/dist/{types-DawhLFwy.d.mts.map → types-OT_Es5mp.d.mts.map} +1 -1
  332. package/dist/{types-i8_uzhMD.d.mts → types-WVmpZBJV.d.mts} +18 -3
  333. package/dist/types-WVmpZBJV.d.mts.map +1 -0
  334. package/dist/{user-DzEUl5zA.mjs → user-C0um7wrg.mjs} +18 -2
  335. package/dist/user-C0um7wrg.mjs.map +1 -0
  336. package/dist/{validate-Dy6nkNls.d.mts → validate-BPAHUSge.d.mts} +10 -2
  337. package/dist/validate-BPAHUSge.d.mts.map +1 -0
  338. package/dist/{validate-JCXcsqiY.mjs → validate-ZP9Dvg0P.mjs} +6 -3
  339. package/dist/validate-ZP9Dvg0P.mjs.map +1 -0
  340. package/dist/{validation-Bq-VyKJg.mjs → validation-CE5i4q0c.mjs} +5 -5
  341. package/dist/{validation-Bq-VyKJg.mjs.map → validation-CE5i4q0c.mjs.map} +1 -1
  342. package/dist/version-Dw0JXu45.mjs +7 -0
  343. package/dist/{version-CnS-Cr8A.mjs.map → version-Dw0JXu45.mjs.map} +1 -1
  344. package/dist/{widgets-Bap1eS1X.mjs → widgets-ClEnYQCH.mjs} +2 -2
  345. package/dist/{widgets-Bap1eS1X.mjs.map → widgets-ClEnYQCH.mjs.map} +1 -1
  346. package/dist/{zod-generator-BSDpkqSH.mjs → zod-generator-Djo_VHCt.mjs} +2 -2
  347. package/dist/{zod-generator-BSDpkqSH.mjs.map → zod-generator-Djo_VHCt.mjs.map} +1 -1
  348. package/package.json +5 -5
  349. package/src/api/handlers/content.ts +107 -8
  350. package/src/api/handlers/index.ts +2 -0
  351. package/src/api/openapi/document.ts +25 -0
  352. package/src/api/schemas/content.ts +33 -0
  353. package/src/astro/integration/index.ts +98 -0
  354. package/src/astro/integration/routes.ts +6 -0
  355. package/src/astro/integration/virtual-modules.ts +39 -0
  356. package/src/astro/integration/vite-config.ts +12 -0
  357. package/src/astro/middleware.ts +28 -0
  358. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  359. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
  360. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
  361. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
  362. package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
  363. package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
  364. package/src/astro/types.ts +8 -1
  365. package/src/bylines/index.ts +57 -0
  366. package/src/cli/commands/export-seed.ts +28 -12
  367. package/src/components/EmDashImage.astro +22 -4
  368. package/src/components/Image.astro +20 -3
  369. package/src/database/migrations/043_content_references.ts +121 -0
  370. package/src/database/migrations/runner.ts +2 -0
  371. package/src/database/repositories/content.ts +225 -67
  372. package/src/database/repositories/index.ts +7 -0
  373. package/src/database/repositories/relation.ts +467 -0
  374. package/src/database/repositories/types.ts +31 -0
  375. package/src/database/repositories/user.ts +18 -0
  376. package/src/database/types.ts +34 -0
  377. package/src/emdash-runtime.ts +141 -42
  378. package/src/index.ts +8 -1
  379. package/src/loader.ts +67 -34
  380. package/src/media/responsive.ts +125 -0
  381. package/src/plugins/cron.ts +3 -2
  382. package/src/plugins/index.ts +5 -0
  383. package/src/plugins/scheduler/node.ts +9 -2
  384. package/src/query.ts +32 -5
  385. package/src/scheduled-publish.ts +153 -0
  386. package/src/seed/apply.ts +16 -6
  387. package/src/seed/types.ts +9 -0
  388. package/src/seed/validate.ts +15 -0
  389. package/src/taxonomies/index.ts +1 -0
  390. package/src/virtual-modules.d.ts +11 -0
  391. package/dist/api-Cs7DAACP.mjs.map +0 -1
  392. package/dist/apply-BWMV4Zmw.mjs.map +0 -1
  393. package/dist/byline-fields-BNy7Ng1U.d.mts.map +0 -1
  394. package/dist/content-CyqOmOzm.mjs.map +0 -1
  395. package/dist/cron-DZovZUnC.mjs.map +0 -1
  396. package/dist/index-CjKdMZ3U.d.mts.map +0 -1
  397. package/dist/loader-Dyx8dhFV.mjs.map +0 -1
  398. package/dist/menus-BKkxXCmd.mjs.map +0 -1
  399. package/dist/query-Ctlq1aOk.mjs.map +0 -1
  400. package/dist/redirects-C0L9JUk4.mjs.map +0 -1
  401. package/dist/runner-pt6Wl-l-.mjs.map +0 -1
  402. package/dist/taxonomies-Crtzy4MT.mjs.map +0 -1
  403. package/dist/types-i8_uzhMD.d.mts.map +0 -1
  404. package/dist/user-DzEUl5zA.mjs.map +0 -1
  405. package/dist/validate-Dy6nkNls.d.mts.map +0 -1
  406. package/dist/validate-JCXcsqiY.mjs.map +0 -1
  407. package/dist/version-CnS-Cr8A.mjs +0 -7
  408. package/src/plugins/scheduler/piggyback.ts +0 -71
@@ -24,7 +24,10 @@ import { isSqlite } from "./database/dialect-helpers.js";
24
24
  import { kyselyLogOption } from "./database/instrumentation.js";
25
25
  import { MIGRATION_RACE_WAIT_MS, runMigrations } from "./database/migrations/runner.js";
26
26
  import { RevisionRepository } from "./database/repositories/revision.js";
27
- import type { ContentItem as ContentItemInternal } from "./database/repositories/types.js";
27
+ import type {
28
+ ContentItem as ContentItemInternal,
29
+ ContentDateField,
30
+ } from "./database/repositories/types.js";
28
31
  import { validateIdentifier } from "./database/validate.js";
29
32
  import { normalizeMediaValue } from "./media/normalize.js";
30
33
  import type { MediaProvider, MediaProviderCapabilities } from "./media/types.js";
@@ -115,6 +118,7 @@ import { validateEncryptionKeyAtStartup } from "./config/secrets.js";
115
118
  import { OptionsRepository } from "./database/repositories/options.js";
116
119
  import {
117
120
  handleContentList,
121
+ handleContentAuthors,
118
122
  handleContentGet,
119
123
  handleContentGetIncludingTrashed,
120
124
  handleContentCreate,
@@ -158,13 +162,12 @@ import {
158
162
  import { normalizeManifestRoute } from "./plugins/manifest-schema.js";
159
163
  import { extractRequestMeta, sanitizeHeadersForSandbox } from "./plugins/request-meta.js";
160
164
  import { PluginRouteRegistry, type RouteMeta } from "./plugins/routes.js";
161
- import { NodeCronScheduler } from "./plugins/scheduler/node.js";
162
- import { PiggybackScheduler } from "./plugins/scheduler/piggyback.js";
163
165
  import type { CronScheduler } from "./plugins/scheduler/types.js";
164
166
  import { PluginStateRepository } from "./plugins/state.js";
165
167
  import { normalizeRegistryConfig } from "./registry/config.js";
166
168
  import { requestCached } from "./request-cache.js";
167
169
  import { getRequestContext } from "./request-context.js";
170
+ import { publishDueContent, type PublishedRef } from "./scheduled-publish.js";
168
171
  import { FTSManager } from "./search/fts-manager.js";
169
172
  import { invalidateSiteSettingsCache } from "./settings/index.js";
170
173
 
@@ -237,6 +240,13 @@ export interface MediaProviderContext {
237
240
  storage: Storage | null;
238
241
  }
239
242
 
243
+ /**
244
+ * Builds the timer-based scheduler that drives cron ticks and maintenance.
245
+ * Injected via `virtual:emdash/scheduler` so the platform — not core — decides
246
+ * whether a long-lived heartbeat exists.
247
+ */
248
+ export type CreateSchedulerFn = (executor: CronExecutor) => CronScheduler;
249
+
240
250
  /**
241
251
  * Dependencies injected from virtual modules (middleware reads these)
242
252
  */
@@ -250,6 +260,16 @@ export interface RuntimeDependencies {
250
260
  sandboxEnabled: boolean;
251
261
  /** sandbox: false escape hatch - load sandboxed plugins in-process */
252
262
  sandboxBypassed?: boolean;
263
+ /**
264
+ * Factory for the timer-based cron/maintenance heartbeat. Supplied by the
265
+ * generated `virtual:emdash/scheduler` module: a `NodeCronScheduler` factory
266
+ * on long-lived runtimes (Node/Bun), or `null` on serverless adapters where
267
+ * an external driver (e.g. the Cloudflare Worker's `scheduled()` Cron
268
+ * Trigger) calls `runScheduledTasks()` instead. When absent or null, the
269
+ * runtime starts no scheduler. Keeping the platform decision in the
270
+ * integration means core has no adapter-specific runtime checks.
271
+ */
272
+ createScheduler?: CreateSchedulerFn | null;
253
273
  /** Media provider entries from virtual module */
254
274
  mediaProviderEntries?: MediaProviderEntry[];
255
275
  sandboxedPluginEntries: SandboxedPluginEntry[];
@@ -479,14 +499,65 @@ export class EmDashRuntime {
479
499
  }
480
500
 
481
501
  /**
482
- * Tick the cron system from request context (piggyback mode).
483
- * Call this from middleware on each request to ensure cron tasks
484
- * execute even when no dedicated scheduler is available.
502
+ * Publish any content whose scheduled time has passed.
503
+ * Returns the items promoted so callers can invalidate their cache tags.
504
+ */
505
+ async publishScheduled(): Promise<PublishedRef[]> {
506
+ return publishDueContent(this.db, {
507
+ publish: (collection, id, options) => this.handleContentPublish(collection, id, options),
508
+ });
509
+ }
510
+
511
+ /**
512
+ * Run the full scheduled-maintenance batch: cron tasks, scheduled
513
+ * publishing, and system cleanup. For request-less drivers — the
514
+ * Cloudflare `scheduled()` handler invokes this from a Cron Trigger.
515
+ * (On Node the timer-based scheduler drives the same work itself.)
516
+ *
517
+ * Each step is independent and non-fatal. Returns the content promoted
518
+ * by the publishing sweep so the caller can purge edge-cache tags.
519
+ *
520
+ * `onPublished` (optional) is awaited after each collection's batch so a
521
+ * request-less driver can invalidate edge-cache tags incrementally rather
522
+ * than only after the whole sweep — bounding stale-cache exposure if the
523
+ * runtime is killed mid-sweep.
485
524
  */
486
- tickCron(): void {
487
- if (this.cronScheduler instanceof PiggybackScheduler) {
488
- this.cronScheduler.onRequest();
525
+ async runScheduledTasks(
526
+ options: {
527
+ onPublished?: (refs: PublishedRef[]) => Promise<void>;
528
+ } = {},
529
+ ): Promise<{ published: PublishedRef[] }> {
530
+ if (this.cronExecutor) {
531
+ try {
532
+ await this.cronExecutor.tick();
533
+ } catch (error) {
534
+ console.error("[cron] Tick failed:", error);
535
+ }
536
+ try {
537
+ await this.cronExecutor.recoverStaleLocks();
538
+ } catch (error) {
539
+ console.error("[cron] Stale lock recovery failed:", error);
540
+ }
489
541
  }
542
+
543
+ let published: PublishedRef[] = [];
544
+ try {
545
+ // Route through the runtime wrapper so content:afterPublish hooks fire.
546
+ published = await publishDueContent(this.db, {
547
+ publish: (collection, id, opts) => this.handleContentPublish(collection, id, opts),
548
+ onPublished: options.onPublished,
549
+ });
550
+ } catch (error) {
551
+ console.error("[scheduled-publish] Sweep failed:", error);
552
+ }
553
+
554
+ try {
555
+ await runSystemCleanup(this.db, this.storage ?? undefined);
556
+ } catch (error) {
557
+ console.error("[cleanup] System cleanup failed:", error);
558
+ }
559
+
560
+ return { published };
490
561
  }
491
562
 
492
563
  /**
@@ -1173,6 +1244,11 @@ export class EmDashRuntime {
1173
1244
 
1174
1245
  let cronExecutor: CronExecutor | null = null;
1175
1246
  let cronScheduler: CronScheduler | null = null;
1247
+ // Populated with the constructed runtime just before this method returns,
1248
+ // so the timer scheduler's cleanup can route scheduled publishing through
1249
+ // the runtime wrapper (firing content:afterPublish hooks). The first tick
1250
+ // is ≥1s out, well after the synchronous assignment below.
1251
+ const runtimeRef: { current: EmDashRuntime | null } = { current: null };
1176
1252
 
1177
1253
  await phase("rt.cron", "Cron init (recovery deferred post-response)", async () => {
1178
1254
  try {
@@ -1198,46 +1274,57 @@ export class EmDashRuntime {
1198
1274
  }
1199
1275
  });
1200
1276
 
1201
- // Detect platform and create appropriate scheduler.
1202
- // On Cloudflare Workers, setTimeout is available but unreliable for
1203
- // long durations use PiggybackScheduler as default.
1204
- // In Node/Bun, use NodeCronScheduler with real timers.
1205
- const isWorkersRuntime =
1206
- typeof globalThis.navigator !== "undefined" &&
1207
- globalThis.navigator.userAgent === "Cloudflare-Workers";
1208
-
1209
- if (isWorkersRuntime) {
1210
- cronScheduler = new PiggybackScheduler(cronExecutor);
1211
- } else {
1212
- cronScheduler = new NodeCronScheduler(cronExecutor);
1213
- }
1214
-
1215
- // Register system cleanup to run alongside each scheduler tick.
1216
- // Pass storage so cleanupPendingUploads can delete orphaned files.
1217
- cronScheduler.setSystemCleanup(async () => {
1218
- try {
1219
- await runSystemCleanup(db, storage ?? undefined);
1220
- } catch (error) {
1221
- // Non-fatal -- individual cleanup failures are already logged
1222
- // by runSystemCleanup. This catches unexpected errors.
1223
- console.error("[cleanup] System cleanup failed:", error);
1224
- }
1225
- });
1277
+ // The platform decides whether a long-lived timer heartbeat exists.
1278
+ // `createScheduler` is injected by the generated virtual:emdash/scheduler
1279
+ // module: a NodeCronScheduler factory on Node/Bun, or null on serverless
1280
+ // adapters (e.g. Cloudflare) where the Worker's `scheduled()` handler
1281
+ // drives runScheduledTasks() instead. No adapter check lives here.
1282
+ if (deps.createScheduler) {
1283
+ const scheduler = deps.createScheduler(cronExecutor);
1284
+ cronScheduler = scheduler;
1285
+
1286
+ // Run scheduled publishing and system cleanup alongside each tick.
1287
+ // Pass storage so cleanupPendingUploads can delete orphaned files.
1288
+ scheduler.setSystemCleanup(async () => {
1289
+ try {
1290
+ // Route through the runtime so content:afterPublish hooks fire.
1291
+ // Falls back to the raw handler if (improbably) the tick beats
1292
+ // the post-construction ref assignment.
1293
+ const runtime = runtimeRef.current;
1294
+ await publishDueContent(db, {
1295
+ publish: runtime
1296
+ ? (collection, id, options) =>
1297
+ runtime.handleContentPublish(collection, id, options)
1298
+ : undefined,
1299
+ });
1300
+ } catch (error) {
1301
+ console.error("[scheduled-publish] Sweep failed:", error);
1302
+ }
1303
+ try {
1304
+ await runSystemCleanup(db, storage ?? undefined);
1305
+ } catch (error) {
1306
+ // Non-fatal -- individual cleanup failures are already logged
1307
+ // by runSystemCleanup. This catches unexpected errors.
1308
+ console.error("[cleanup] System cleanup failed:", error);
1309
+ }
1310
+ });
1226
1311
 
1227
- // Add cron reschedule callback (merges with existing factory options)
1228
- pipeline.setContextFactory({
1229
- cronReschedule: () => cronScheduler?.reschedule(),
1230
- });
1312
+ // Add cron reschedule callback (merges with existing factory options)
1313
+ pipeline.setContextFactory({
1314
+ cronReschedule: () => cronScheduler?.reschedule(),
1315
+ });
1231
1316
 
1232
- // Start the scheduler
1233
- await cronScheduler.start();
1317
+ // start() is void on the timer scheduler but the interface
1318
+ // allows a promise (alarm-backed schedulers); we don't block on it.
1319
+ void scheduler.start();
1320
+ }
1234
1321
  } catch (error) {
1235
1322
  console.warn("[cron] Failed to initialize cron system:", error);
1236
1323
  // Non-fatal — CMS works without cron
1237
1324
  }
1238
1325
  });
1239
1326
 
1240
- return new EmDashRuntime({
1327
+ const runtime = new EmDashRuntime({
1241
1328
  db,
1242
1329
  storage,
1243
1330
  // Include bypassed sandboxed plugins in configuredPlugins so route
@@ -1260,6 +1347,10 @@ export class EmDashRuntime {
1260
1347
  runtimeDeps: deps,
1261
1348
  pipelineRef,
1262
1349
  });
1350
+ // Hand the constructed instance to the scheduler-cleanup closure so the
1351
+ // timer-driven sweep can fire publish hooks (see runtimeRef above).
1352
+ runtimeRef.current = runtime;
1353
+ return runtime;
1263
1354
  }
1264
1355
 
1265
1356
  /**
@@ -2174,11 +2265,19 @@ export class EmDashRuntime {
2174
2265
  order?: "asc" | "desc";
2175
2266
  locale?: string;
2176
2267
  q?: string;
2268
+ authorId?: string;
2269
+ dateField?: ContentDateField;
2270
+ dateFrom?: string;
2271
+ dateTo?: string;
2177
2272
  },
2178
2273
  ) {
2179
2274
  return handleContentList(this.db, collection, params);
2180
2275
  }
2181
2276
 
2277
+ async handleContentAuthors(collection: string) {
2278
+ return handleContentAuthors(this.db, collection);
2279
+ }
2280
+
2182
2281
  async handleContentGet(collection: string, id: string, locale?: string) {
2183
2282
  const result = await handleContentGet(this.db, collection, id, locale);
2184
2283
  return this.hydrateDraftData(result);
@@ -2553,7 +2652,7 @@ export class EmDashRuntime {
2553
2652
  async handleContentPublish(
2554
2653
  collection: string,
2555
2654
  id: string,
2556
- options: { publishedAt?: string } = {},
2655
+ options: { publishedAt?: string; requireScheduledDue?: boolean } = {},
2557
2656
  ) {
2558
2657
  const result = await handleContentPublish(this.db, collection, id, options);
2559
2658
 
package/src/index.ts CHANGED
@@ -46,6 +46,7 @@ export type {
46
46
  // API handlers
47
47
  export {
48
48
  handleContentList,
49
+ handleContentAuthors,
49
50
  handleContentGet,
50
51
  handleContentGetIncludingTrashed,
51
52
  handleContentCreate,
@@ -201,6 +202,8 @@ export {
201
202
  PluginManager,
202
203
  createPluginManager,
203
204
  PluginRouteError,
205
+ // Scheduler (Node timer heartbeat — used by virtual:emdash/scheduler)
206
+ NodeCronScheduler,
204
207
  // Sandbox
205
208
  NoopSandboxRunner,
206
209
  SandboxNotAvailableError,
@@ -253,6 +256,10 @@ export type {
253
256
  CollectionCommentSettings,
254
257
  StoredComment,
255
258
 
259
+ // Scheduler types
260
+ CronScheduler,
261
+ SystemCleanupFn,
262
+
256
263
  // Sandbox runtime types
257
264
  SandboxRunner,
258
265
  SandboxedPluginInstance,
@@ -406,7 +413,7 @@ export type {
406
413
  } from "./menus/types.js";
407
414
 
408
415
  // Bylines
409
- export { getByline, getBylineBySlug } from "./bylines/index.js";
416
+ export { getByline, getBylineBySlug, getEntriesByByline } from "./bylines/index.js";
410
417
  export type { BylineSummary, ContentBylineCredit } from "./database/repositories/types.js";
411
418
 
412
419
  // Taxonomies
package/src/loader.ts CHANGED
@@ -540,12 +540,16 @@ export interface CollectionFilter {
540
540
  */
541
541
  cursor?: string;
542
542
  /**
543
- * Filter by field values, taxonomy terms, or ranges.
543
+ * Filter by field values, taxonomy terms, byline credits, or ranges.
544
544
  *
545
545
  * Taxonomy names are detected automatically and filtered via JOIN.
546
+ * The reserved `byline` key filters by byline credit (any credit, not
547
+ * just the primary one) via the `_emdash_content_bylines` junction
548
+ * table; its value is one or more byline translation groups.
546
549
  * Other keys are treated as column filters on the content table.
547
550
  *
548
551
  * @example { category: 'news' } - taxonomy term
552
+ * @example { byline: '01HXYZ...' } - entries credited to a byline (any position)
549
553
  * @example { series: 'main' } - exact match on a content field
550
554
  * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - date range
551
555
  */
@@ -673,13 +677,16 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
673
677
 
674
678
  // Build cursor condition if cursor is provided
675
679
  const cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;
676
- const cursorConditionPrefixed = cursor
677
- ? buildCursorCondition(cursor, orderBy, tableName)
678
- : null;
679
680
 
680
- // Separate taxonomy filters from field filters
681
+ // Separate taxonomy / byline filters from field filters
681
682
  let result: { rows: Record<string, unknown>[] };
682
683
  let taxonomyFilter: { name: string; slugs: string[] } | null = null;
684
+ // A byline filter matches entries credited to any of the given
685
+ // byline translation groups via the `_emdash_content_bylines`
686
+ // junction table. `null` means no byline filter; an empty
687
+ // `groups` array means the filter was requested but matches
688
+ // nothing (short-circuited to an empty result below).
689
+ let bylineFilter: { groups: string[] } | null = null;
683
690
  const fieldFilters: Record<string, WhereValue> = {};
684
691
 
685
692
  if (where && Object.keys(where).length > 0) {
@@ -687,7 +694,16 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
687
694
 
688
695
  for (const [key, value] of Object.entries(where)) {
689
696
  if (value == null) continue;
690
- if (taxNames.has(key)) {
697
+ if (key === "byline") {
698
+ if (isWhereRange(value)) {
699
+ console.warn(
700
+ `[emdash] where filter: range operators are not supported on "byline", ignored`,
701
+ );
702
+ continue;
703
+ }
704
+ const groups = Array.isArray(value) ? value : [value];
705
+ bylineFilter = { groups };
706
+ } else if (taxNames.has(key)) {
691
707
  if (isWhereRange(value)) {
692
708
  console.warn(
693
709
  `[emdash] where filter: range operators are not supported on taxonomy "${key}", ignored`,
@@ -708,35 +724,27 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
708
724
  }
709
725
  }
710
726
 
711
- if (taxonomyFilter) {
712
- const orderByClause = buildOrderByClause(orderBy, tableName);
713
- const statusCondition = buildStatusCondition(db, status, tableName);
714
- const localeCondition = locale
715
- ? sql`AND ${sql.ref(tableName)}.locale = ${locale}`
716
- : sql``;
717
- const cursorCond = cursorConditionPrefixed ? sql`AND ${cursorConditionPrefixed}` : sql``;
718
- const fieldConds = buildFieldConditions(fieldFilters, tableName);
719
- const fieldCondsSQL =
720
- fieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;
727
+ // A byline or taxonomy filter with no values matches nothing —
728
+ // short-circuit before building SQL (an empty `IN ()` is invalid
729
+ // SQL on both dialects).
730
+ if (
731
+ (bylineFilter && bylineFilter.groups.length === 0) ||
732
+ (taxonomyFilter && taxonomyFilter.slugs.length === 0)
733
+ ) {
734
+ return { entries: [], cacheHint: { tags: [type] } };
735
+ }
721
736
 
722
- result = await sql<Record<string, unknown>>`
723
- SELECT DISTINCT ${sql.ref(tableName)}.* FROM ${sql.ref(tableName)}
724
- INNER JOIN content_taxonomies ct
725
- ON ct.collection = ${type}
726
- AND ct.entry_id = ${sql.ref(tableName)}.id
727
- INNER JOIN taxonomies t
728
- ON t.id = ct.taxonomy_id
729
- WHERE ${sql.ref(tableName)}.deleted_at IS NULL
730
- AND ${statusCondition}
731
- ${localeCondition}
732
- ${cursorCond}
733
- AND t.name = ${taxonomyFilter.name}
734
- AND t.slug IN (${sql.join(taxonomyFilter.slugs.map((s) => sql`${s}`))})
735
- ${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}
736
- ${orderByClause}
737
- ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
738
- `.execute(db);
739
- } else {
737
+ {
738
+ // Taxonomy and byline filters are applied as correlated
739
+ // `EXISTS` semi-joins rather than `INNER JOIN ... DISTINCT`.
740
+ // A join fan-out would force `SELECT DISTINCT table.*`, and
741
+ // Postgres cannot apply DISTINCT to a row containing a `json`
742
+ // column (no equality operator), so the join approach throws
743
+ // there. EXISTS matches "credited/tagged at least once"
744
+ // without duplicating rows, needs no DISTINCT, and works on
745
+ // both SQLite and Postgres. The base query stays a single-
746
+ // table `SELECT *`, so all field/status/locale/cursor/order
747
+ // conditions reference unprefixed columns as before.
740
748
  const orderByClause = buildOrderByClause(orderBy);
741
749
  const statusCondition = buildStatusCondition(db, status);
742
750
  const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
@@ -745,12 +753,37 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
745
753
  const fieldCondsSQL =
746
754
  fieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;
747
755
 
756
+ const taxonomyCond = taxonomyFilter
757
+ ? sql`AND EXISTS (
758
+ SELECT 1 FROM content_taxonomies ct
759
+ INNER JOIN taxonomies t ON t.id = ct.taxonomy_id
760
+ WHERE ct.collection = ${type}
761
+ AND ct.entry_id = ${sql.ref(tableName)}.id
762
+ AND t.name = ${taxonomyFilter.name}
763
+ AND t.slug IN (${sql.join(taxonomyFilter.slugs.map((s) => sql`${s}`))})
764
+ )`
765
+ : sql``;
766
+
767
+ // `_emdash_content_bylines.byline_id` stores the byline's
768
+ // translation_group (migration 040), so a credit spans every
769
+ // locale variant of the byline and we match the group directly.
770
+ const bylineCond = bylineFilter
771
+ ? sql`AND EXISTS (
772
+ SELECT 1 FROM _emdash_content_bylines cb
773
+ WHERE cb.collection_slug = ${type}
774
+ AND cb.content_id = ${sql.ref(tableName)}.id
775
+ AND cb.byline_id IN (${sql.join(bylineFilter.groups.map((g) => sql`${g}`))})
776
+ )`
777
+ : sql``;
778
+
748
779
  result = await sql<Record<string, unknown>>`
749
780
  SELECT * FROM ${sql.ref(tableName)}
750
781
  WHERE deleted_at IS NULL
751
782
  AND ${statusCondition}
752
783
  ${localeFilter}
753
784
  ${cursorCond}
785
+ ${taxonomyCond}
786
+ ${bylineCond}
754
787
  ${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}
755
788
  ${orderByClause}
756
789
  ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
@@ -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,
@@ -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;