emdash 0.25.1 → 0.26.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 (346) hide show
  1. package/dist/api/route-utils.d.mts +3 -3
  2. package/dist/api/route-utils.mjs +11 -11
  3. package/dist/{api-D2_sbavn.mjs → api-BMfveMoB.mjs} +15 -15
  4. package/dist/{api-D2_sbavn.mjs.map → api-BMfveMoB.mjs.map} +1 -1
  5. package/dist/{apply-Bk2mAuLn.mjs → apply--IVNSdXn.mjs} +18 -16
  6. package/dist/apply--IVNSdXn.mjs.map +1 -0
  7. package/dist/astro/index.d.mts +7 -7
  8. package/dist/astro/index.mjs +2 -1
  9. package/dist/astro/index.mjs.map +1 -1
  10. package/dist/astro/middleware/auth.d.mts +7 -7
  11. package/dist/astro/middleware/auth.mjs +2 -2
  12. package/dist/astro/middleware/redirect.mjs +4 -4
  13. package/dist/astro/middleware.d.mts +1 -1
  14. package/dist/astro/middleware.mjs +85 -85
  15. package/dist/astro/middleware.mjs.map +1 -1
  16. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
  17. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
  18. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  19. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  20. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  21. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
  22. package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
  23. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
  24. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +10 -10
  25. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +10 -10
  26. package/dist/astro/routes/api/admin/bylines/index.mjs +10 -10
  27. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +8 -8
  28. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  29. package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -7
  30. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  31. package/dist/astro/routes/api/admin/comments/index.mjs +7 -7
  32. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  33. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  34. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  35. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  36. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +28 -28
  37. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +28 -28
  38. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -28
  39. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -28
  40. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -28
  41. package/dist/astro/routes/api/admin/plugins/index.mjs +28 -28
  42. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  43. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -28
  44. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -28
  45. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -28
  46. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -28
  47. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -29
  48. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -28
  49. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -29
  50. package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -28
  51. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -28
  52. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  53. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -28
  54. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  55. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  56. package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
  57. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  58. package/dist/astro/routes/api/admin/users/index.mjs +3 -3
  59. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  60. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  61. package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
  62. package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
  63. package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
  64. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  65. package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
  66. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  67. package/dist/astro/routes/api/auth/me.mjs +4 -4
  68. package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
  69. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  70. package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
  71. package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
  72. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
  73. package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
  74. package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
  75. package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
  76. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  77. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +7 -7
  78. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +7 -7
  79. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  80. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  81. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  82. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  83. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
  84. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
  85. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  86. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +4 -4
  88. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +9 -9
  89. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  91. package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
  92. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  93. package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
  94. package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
  95. package/dist/astro/routes/api/dashboard.mjs +8 -8
  96. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  97. package/dist/astro/routes/api/import/probe.d.mts +2 -2
  98. package/dist/astro/routes/api/import/probe.mjs +4 -4
  99. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  100. package/dist/astro/routes/api/import/wordpress/execute.d.mts +7 -7
  101. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  102. package/dist/astro/routes/api/import/wordpress/media.d.mts +7 -7
  103. package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
  104. package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
  105. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +5 -5
  106. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
  107. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +7 -7
  108. package/dist/astro/routes/api/manifest.mjs +3 -3
  109. package/dist/astro/routes/api/mcp.mjs +34 -34
  110. package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
  111. package/dist/astro/routes/api/media/_id_.mjs +4 -4
  112. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  113. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  114. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  115. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  116. package/dist/astro/routes/api/media/upload-url.mjs +4 -4
  117. package/dist/astro/routes/api/media.mjs +5 -5
  118. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -6
  119. package/dist/astro/routes/api/menus/_name_/items.mjs +6 -6
  120. package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -6
  121. package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -6
  122. package/dist/astro/routes/api/menus/_name_.mjs +6 -6
  123. package/dist/astro/routes/api/menus/index.mjs +6 -6
  124. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  125. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  126. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  127. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  128. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  129. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  130. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  131. package/dist/astro/routes/api/openapi.json.mjs +2 -2
  132. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  133. package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
  134. package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
  135. package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
  136. package/dist/astro/routes/api/redirects/index.mjs +7 -7
  137. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  138. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  139. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -28
  140. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -28
  141. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -28
  142. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -28
  143. package/dist/astro/routes/api/schema/collections/index.mjs +28 -28
  144. package/dist/astro/routes/api/schema/index.mjs +6 -6
  145. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -28
  146. package/dist/astro/routes/api/schema/orphans/index.mjs +28 -28
  147. package/dist/astro/routes/api/search/enable.mjs +7 -7
  148. package/dist/astro/routes/api/search/index.mjs +6 -6
  149. package/dist/astro/routes/api/search/rebuild.mjs +7 -7
  150. package/dist/astro/routes/api/search/stats.mjs +6 -6
  151. package/dist/astro/routes/api/search/suggest.mjs +6 -6
  152. package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
  153. package/dist/astro/routes/api/sections/index.mjs +6 -6
  154. package/dist/astro/routes/api/settings/email.mjs +4 -4
  155. package/dist/astro/routes/api/settings.mjs +9 -9
  156. package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
  157. package/dist/astro/routes/api/setup/admin.mjs +3 -3
  158. package/dist/astro/routes/api/setup/dev-bypass.mjs +17 -17
  159. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  160. package/dist/astro/routes/api/setup/index.mjs +18 -18
  161. package/dist/astro/routes/api/setup/status.mjs +3 -3
  162. package/dist/astro/routes/api/snapshot.mjs +3 -3
  163. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +9 -9
  164. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +9 -9
  165. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +9 -9
  166. package/dist/astro/routes/api/taxonomies/index.mjs +9 -9
  167. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  168. package/dist/astro/routes/api/typegen.mjs +5 -5
  169. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
  170. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
  171. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
  172. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  173. package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
  174. package/dist/astro/routes/api/widget-components.mjs +2 -2
  175. package/dist/astro/routes/robots.txt.mjs +5 -5
  176. package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -9
  177. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  178. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  179. package/dist/astro/types.d.mts +7 -7
  180. package/dist/{authorize-CuxZ0i2t.mjs → authorize-BZnOGJFo.mjs} +2 -2
  181. package/dist/{authorize-CuxZ0i2t.mjs.map → authorize-BZnOGJFo.mjs.map} +1 -1
  182. package/dist/{byline-C-YajfV7.mjs → byline-LL8lyqdb.mjs} +6 -6
  183. package/dist/{byline-C-YajfV7.mjs.map → byline-LL8lyqdb.mjs.map} +1 -1
  184. package/dist/{bylines-D4KADDAi.mjs → bylines-BmJ7Vg7J.mjs} +4 -4
  185. package/dist/{bylines-D4KADDAi.mjs.map → bylines-BmJ7Vg7J.mjs.map} +1 -1
  186. package/dist/{bylines-CPc_q2Vd.mjs → bylines-DE47vP8d.mjs} +5 -5
  187. package/dist/{bylines-CPc_q2Vd.mjs.map → bylines-DE47vP8d.mjs.map} +1 -1
  188. package/dist/{cache-C35VWD5D.mjs → cache-D9jF5bYw.mjs} +2 -2
  189. package/dist/{cache-C35VWD5D.mjs.map → cache-D9jF5bYw.mjs.map} +1 -1
  190. package/dist/{chunks-BHgcHh2C.mjs → chunks-OCG163rN.mjs} +2 -2
  191. package/dist/{chunks-BHgcHh2C.mjs.map → chunks-OCG163rN.mjs.map} +1 -1
  192. package/dist/cli/index.mjs +16 -16
  193. package/dist/{comment-BDx50EX5.mjs → comment-D9BLDpZI.mjs} +3 -3
  194. package/dist/{comment-BDx50EX5.mjs.map → comment-D9BLDpZI.mjs.map} +1 -1
  195. package/dist/{comment-reaction-Gv_pgfI3.mjs → comment-reaction-Drl9SkNu.mjs} +2 -2
  196. package/dist/{comment-reaction-Gv_pgfI3.mjs.map → comment-reaction-Drl9SkNu.mjs.map} +1 -1
  197. package/dist/{comments-CXHuQ6jI.mjs → comments-BjJ9SKGU.mjs} +3 -3
  198. package/dist/{comments-CXHuQ6jI.mjs.map → comments-BjJ9SKGU.mjs.map} +1 -1
  199. package/dist/{content-B43-YrIq.mjs → content-ncUWWjna.mjs} +4 -4
  200. package/dist/{content-B43-YrIq.mjs.map → content-ncUWWjna.mjs.map} +1 -1
  201. package/dist/{context-BW3N7wyt.mjs → context-C_g-iJVA.mjs} +7 -7
  202. package/dist/{context-BW3N7wyt.mjs.map → context-C_g-iJVA.mjs.map} +1 -1
  203. package/dist/{dashboard-FIe0S512.mjs → dashboard-CotyqQrW.mjs} +4 -4
  204. package/dist/{dashboard-FIe0S512.mjs.map → dashboard-CotyqQrW.mjs.map} +1 -1
  205. package/dist/db/index.d.mts +2 -2
  206. package/dist/db/index.mjs +1 -1
  207. package/dist/{error-DXnK2wF4.mjs → error-rVGiqWDt.mjs} +2 -2
  208. package/dist/{error-DXnK2wF4.mjs.map → error-rVGiqWDt.mjs.map} +1 -1
  209. package/dist/{field-defs-cache-CYYx8xlG.mjs → field-defs-cache-ljXzJCfc.mjs} +2 -2
  210. package/dist/{field-defs-cache-CYYx8xlG.mjs.map → field-defs-cache-ljXzJCfc.mjs.map} +1 -1
  211. package/dist/{fts-manager-DW_NR-Fs.mjs → fts-manager-BbCCbEKA.mjs} +2 -2
  212. package/dist/{fts-manager-DW_NR-Fs.mjs.map → fts-manager-BbCCbEKA.mjs.map} +1 -1
  213. package/dist/{index-Cz6_JBL3.d.mts → index-BRNabKrx.d.mts} +36 -11
  214. package/dist/index-BRNabKrx.d.mts.map +1 -0
  215. package/dist/{index-qbL02uhs.d.mts → index-Dt0OKJB4.d.mts} +2 -2
  216. package/dist/{index-qbL02uhs.d.mts.map → index-Dt0OKJB4.d.mts.map} +1 -1
  217. package/dist/index.d.mts +8 -8
  218. package/dist/index.mjs +38 -38
  219. package/dist/{load-DMFi4n1p.mjs → load-DMjXghbZ.mjs} +2 -2
  220. package/dist/{load-DMFi4n1p.mjs.map → load-DMjXghbZ.mjs.map} +1 -1
  221. package/dist/{loader-DrpofRon.mjs → loader-BA_zhkS_.mjs} +81 -18
  222. package/dist/loader-BA_zhkS_.mjs.map +1 -0
  223. package/dist/{manifest-schema-BOfPXse0.mjs → manifest-schema-kPGX7VS-.mjs} +3 -1
  224. package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
  225. package/dist/media/local-runtime.d.mts +7 -7
  226. package/dist/media/local-runtime.mjs +5 -5
  227. package/dist/{media-BjKxjzk8.mjs → media-C5t_eCnk.mjs} +2 -2
  228. package/dist/{media-BjKxjzk8.mjs.map → media-C5t_eCnk.mjs.map} +1 -1
  229. package/dist/{menus-8O6xNcYW.mjs → menus-CBb6UrMU.mjs} +3 -3
  230. package/dist/{menus-8O6xNcYW.mjs.map → menus-CBb6UrMU.mjs.map} +1 -1
  231. package/dist/{menus-CxqJ-z-N.mjs → menus-CYH09a41.mjs} +50 -68
  232. package/dist/menus-CYH09a41.mjs.map +1 -0
  233. package/dist/{object-cache-SEb2IM4Z.mjs → object-cache-CHbHv83-.mjs} +8 -4
  234. package/dist/object-cache-CHbHv83-.mjs.map +1 -0
  235. package/dist/{options-BvClpP5Q.d.mts → options-41nCWqi9.d.mts} +2 -2
  236. package/dist/{options-BvClpP5Q.d.mts.map → options-41nCWqi9.d.mts.map} +1 -1
  237. package/dist/page/index.d.mts +5 -2
  238. package/dist/page/index.d.mts.map +1 -1
  239. package/dist/page/index.mjs +3 -2
  240. package/dist/page/index.mjs.map +1 -1
  241. package/dist/{parse-DqMWHQ8x.mjs → parse-BwAdS7FZ.mjs} +2 -2
  242. package/dist/{parse-DqMWHQ8x.mjs.map → parse-BwAdS7FZ.mjs.map} +1 -1
  243. package/dist/plugin-types.d.mts +4 -2
  244. package/dist/plugin-types.d.mts.map +1 -1
  245. package/dist/plugin-utils.d.mts +7 -7
  246. package/dist/plugins/adapt-sandbox-entry.d.mts +7 -7
  247. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  248. package/dist/{query-34pfm02Q.mjs → query-BpQb-blG.mjs} +14 -14
  249. package/dist/{query-34pfm02Q.mjs.map → query-BpQb-blG.mjs.map} +1 -1
  250. package/dist/{rate-limit-CBQK4ygJ.mjs → rate-limit-Dap6HMNM.mjs} +2 -2
  251. package/dist/{rate-limit-CBQK4ygJ.mjs.map → rate-limit-Dap6HMNM.mjs.map} +1 -1
  252. package/dist/{redirect-C1KVGYWe.mjs → redirect-_M0trWoo.mjs} +2 -2
  253. package/dist/{redirect-C1KVGYWe.mjs.map → redirect-_M0trWoo.mjs.map} +1 -1
  254. package/dist/{redirects-DgnypVir.mjs → redirects-w0V9vj_j.mjs} +3 -3
  255. package/dist/{redirects-DgnypVir.mjs.map → redirects-w0V9vj_j.mjs.map} +1 -1
  256. package/dist/{registry-3aOAS4My.mjs → registry-g8yYt4cC.mjs} +4 -4
  257. package/dist/{registry-3aOAS4My.mjs.map → registry-g8yYt4cC.mjs.map} +1 -1
  258. package/dist/{runner-CEtq-6Sm.d.mts → runner-BbR3DfrL.d.mts} +2 -2
  259. package/dist/{runner-CEtq-6Sm.d.mts.map → runner-BbR3DfrL.d.mts.map} +1 -1
  260. package/dist/{runner-BsquExlK.mjs → runner-DPdt_Wpa.mjs} +250 -178
  261. package/dist/runner-DPdt_Wpa.mjs.map +1 -0
  262. package/dist/runtime.d.mts +7 -7
  263. package/dist/runtime.mjs +2 -2
  264. package/dist/{schema-BGwx8AQY.mjs → schema-DoLd3j2e.mjs} +5 -5
  265. package/dist/{schema-BGwx8AQY.mjs.map → schema-DoLd3j2e.mjs.map} +1 -1
  266. package/dist/{search-Gl747zMP.mjs → search-BR0ZeZmh.mjs} +4 -4
  267. package/dist/{search-Gl747zMP.mjs.map → search-BR0ZeZmh.mjs.map} +1 -1
  268. package/dist/{sections-C-umuuLj.mjs → sections-5J2YJPqJ.mjs} +3 -3
  269. package/dist/{sections-C-umuuLj.mjs.map → sections-5J2YJPqJ.mjs.map} +1 -1
  270. package/dist/seed/index.d.mts +2 -2
  271. package/dist/seed/index.mjs +15 -15
  272. package/dist/{seo-BNzkiK_W.mjs → seo-DKeXHkzE.mjs} +3 -3
  273. package/dist/{seo-BNzkiK_W.mjs.map → seo-DKeXHkzE.mjs.map} +1 -1
  274. package/dist/{service-CQHK_10l.mjs → service-BbO2s8gT.mjs} +2 -2
  275. package/dist/{service-CQHK_10l.mjs.map → service-BbO2s8gT.mjs.map} +1 -1
  276. package/dist/{settings-fPSR6dJa.mjs → settings-BMNr9Dmi.mjs} +3 -3
  277. package/dist/{settings-fPSR6dJa.mjs.map → settings-BMNr9Dmi.mjs.map} +1 -1
  278. package/dist/{settings-C4tXRYiG.mjs → settings-DJeki-97.mjs} +4 -4
  279. package/dist/{settings-C4tXRYiG.mjs.map → settings-DJeki-97.mjs.map} +1 -1
  280. package/dist/{taxonomies-DbAtL6m4.mjs → taxonomies-CM_fUqLU.mjs} +5 -4
  281. package/dist/taxonomies-CM_fUqLU.mjs.map +1 -0
  282. package/dist/{taxonomies-n8w72lLQ.mjs → taxonomies-D13i2ctt.mjs} +75 -20
  283. package/dist/taxonomies-D13i2ctt.mjs.map +1 -0
  284. package/dist/{taxonomy-BBUlGug5.mjs → taxonomy-BU0LjdVb.mjs} +4 -4
  285. package/dist/{taxonomy-BBUlGug5.mjs.map → taxonomy-BU0LjdVb.mjs.map} +1 -1
  286. package/dist/{types-h2Cwbzi2.d.mts → types-BYnDBVwe.d.mts} +24 -5
  287. package/dist/{types-h2Cwbzi2.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
  288. package/dist/{types-CmI1OyQg.mjs → types-CZgK3EMW.mjs} +2 -2
  289. package/dist/{types-CmI1OyQg.mjs.map → types-CZgK3EMW.mjs.map} +1 -1
  290. package/dist/{types-B-gIxFCE.d.mts → types-xIfVRNLp.d.mts} +38 -1
  291. package/dist/types-xIfVRNLp.d.mts.map +1 -0
  292. package/dist/{user-JAuc8VUf.mjs → user-DkG2jKCp.mjs} +3 -3
  293. package/dist/{user-JAuc8VUf.mjs.map → user-DkG2jKCp.mjs.map} +1 -1
  294. package/dist/{validate-Va_An3qb.d.mts → validate-CG0V0wNR.d.mts} +2 -2
  295. package/dist/{validate-Va_An3qb.d.mts.map → validate-CG0V0wNR.d.mts.map} +1 -1
  296. package/dist/{validate-BzXHWnkV.mjs → validate-CuJ1Ps9j.mjs} +2 -2
  297. package/dist/{validate-BzXHWnkV.mjs.map → validate-CuJ1Ps9j.mjs.map} +1 -1
  298. package/dist/{validation-B6DKPTtU.mjs → validation-CwUfTdGF.mjs} +5 -5
  299. package/dist/{validation-B6DKPTtU.mjs.map → validation-CwUfTdGF.mjs.map} +1 -1
  300. package/dist/version-BoNBItP_.mjs +7 -0
  301. package/dist/{version-DHdCA24u.mjs.map → version-BoNBItP_.mjs.map} +1 -1
  302. package/dist/{widgets-C2RbFN0G.mjs → widgets-BfOZ-ajY.mjs} +2 -2
  303. package/dist/{widgets-C2RbFN0G.mjs.map → widgets-BfOZ-ajY.mjs.map} +1 -1
  304. package/dist/{zod-generator-xEHODMJf.mjs → zod-generator-gFsRuNNy.mjs} +6 -3
  305. package/dist/zod-generator-gFsRuNNy.mjs.map +1 -0
  306. package/package.json +5 -5
  307. package/src/api/handlers/taxonomies.ts +5 -1
  308. package/src/astro/integration/virtual-modules.ts +15 -0
  309. package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
  310. package/src/components/EmDashHead.astro +23 -3
  311. package/src/components/JsonLdScript.astro +9 -0
  312. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +16 -0
  313. package/src/database/migrations/046_media_usage_index.ts +115 -0
  314. package/src/database/migrations/047_restore_taxonomy_parent_index.ts +29 -0
  315. package/src/database/migrations/runner.ts +4 -0
  316. package/src/database/repositories/media-usage.ts +444 -0
  317. package/src/database/types.ts +39 -0
  318. package/src/emdash-runtime.ts +61 -52
  319. package/src/index.ts +3 -0
  320. package/src/loader.ts +110 -31
  321. package/src/media/usage/extractor.ts +302 -0
  322. package/src/media/usage/types.ts +46 -0
  323. package/src/object-cache/index.ts +8 -2
  324. package/src/page/metadata.ts +23 -3
  325. package/src/plugin-types.ts +10 -0
  326. package/src/plugins/define-plugin.ts +14 -5
  327. package/src/plugins/hooks.ts +53 -65
  328. package/src/plugins/index.ts +5 -0
  329. package/src/plugins/manager.ts +22 -0
  330. package/src/plugins/manifest-schema.ts +2 -0
  331. package/src/plugins/types.ts +35 -3
  332. package/src/schema/zod-generator.ts +9 -1
  333. package/src/seed/apply.ts +6 -0
  334. package/src/taxonomies/index.ts +121 -18
  335. package/dist/apply-Bk2mAuLn.mjs.map +0 -1
  336. package/dist/index-Cz6_JBL3.d.mts.map +0 -1
  337. package/dist/loader-DrpofRon.mjs.map +0 -1
  338. package/dist/manifest-schema-BOfPXse0.mjs.map +0 -1
  339. package/dist/menus-CxqJ-z-N.mjs.map +0 -1
  340. package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
  341. package/dist/runner-BsquExlK.mjs.map +0 -1
  342. package/dist/taxonomies-DbAtL6m4.mjs.map +0 -1
  343. package/dist/taxonomies-n8w72lLQ.mjs.map +0 -1
  344. package/dist/types-B-gIxFCE.d.mts.map +0 -1
  345. package/dist/version-DHdCA24u.mjs +0 -7
  346. package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
@@ -2857,11 +2857,25 @@ export class EmDashRuntime {
2857
2857
  }
2858
2858
 
2859
2859
  async handleContentSchedule(collection: string, id: string, scheduledAt: string) {
2860
- return handleContentSchedule(this.db, collection, id, scheduledAt);
2860
+ const result = await handleContentSchedule(this.db, collection, id, scheduledAt);
2861
+
2862
+ // Run afterSchedule hooks (fire-and-forget)
2863
+ if (result.success && result.data) {
2864
+ this.runAfterScheduleHooks(contentItemToRecord(result.data.item), collection);
2865
+ }
2866
+
2867
+ return result;
2861
2868
  }
2862
2869
 
2863
2870
  async handleContentUnschedule(collection: string, id: string) {
2864
- return handleContentUnschedule(this.db, collection, id);
2871
+ const result = await handleContentUnschedule(this.db, collection, id);
2872
+
2873
+ // Run afterUnschedule hooks (fire-and-forget)
2874
+ if (result.success && result.data) {
2875
+ this.runAfterUnscheduleHooks(contentItemToRecord(result.data.item), collection);
2876
+ }
2877
+
2878
+ return result;
2865
2879
  }
2866
2880
 
2867
2881
  async handleContentCountScheduled(collection: string) {
@@ -3320,14 +3334,41 @@ export class EmDashRuntime {
3320
3334
  }
3321
3335
  }
3322
3336
 
3323
- private runAfterPublishHooks(content: Record<string, unknown>, collection: string): void {
3337
+ private runDeferredContentHook(
3338
+ name:
3339
+ | "content:afterPublish"
3340
+ | "content:afterUnpublish"
3341
+ | "content:afterRestore"
3342
+ | "content:afterSchedule"
3343
+ | "content:afterUnschedule",
3344
+ content: Record<string, unknown>,
3345
+ collection: string,
3346
+ ): void {
3347
+ const label = name.slice("content:".length);
3348
+
3324
3349
  after(async () => {
3325
3350
  // Trusted plugins
3326
- if (this.hooks.hasHooks("content:afterPublish")) {
3351
+ if (this.hooks.hasHooks(name)) {
3327
3352
  try {
3328
- await this.hooks.runContentAfterPublish(content, collection);
3353
+ switch (name) {
3354
+ case "content:afterPublish":
3355
+ await this.hooks.runContentAfterPublish(content, collection);
3356
+ break;
3357
+ case "content:afterUnpublish":
3358
+ await this.hooks.runContentAfterUnpublish(content, collection);
3359
+ break;
3360
+ case "content:afterRestore":
3361
+ await this.hooks.runContentAfterRestore(content, collection);
3362
+ break;
3363
+ case "content:afterSchedule":
3364
+ await this.hooks.runContentAfterSchedule(content, collection);
3365
+ break;
3366
+ case "content:afterUnschedule":
3367
+ await this.hooks.runContentAfterUnschedule(content, collection);
3368
+ break;
3369
+ }
3329
3370
  } catch (err) {
3330
- console.error("EmDash afterPublish hook error:", err);
3371
+ console.error(`EmDash ${label} hook error:`, err);
3331
3372
  }
3332
3373
  }
3333
3374
 
@@ -3340,9 +3381,9 @@ export class EmDashRuntime {
3340
3381
  tasks.push(
3341
3382
  (async () => {
3342
3383
  try {
3343
- await plugin.invokeHook("content:afterPublish", { content, collection });
3384
+ await plugin.invokeHook(name, { content, collection });
3344
3385
  } catch (err) {
3345
- console.error(`EmDash: Sandboxed plugin ${pluginId} afterPublish error:`, err);
3386
+ console.error(`EmDash: Sandboxed plugin ${pluginId} ${label} error:`, err);
3346
3387
  }
3347
3388
  })(),
3348
3389
  );
@@ -3351,56 +3392,24 @@ export class EmDashRuntime {
3351
3392
  });
3352
3393
  }
3353
3394
 
3354
- private runAfterUnpublishHooks(content: Record<string, unknown>, collection: string): void {
3355
- // Trusted plugins
3356
- if (this.hooks.hasHooks("content:afterUnpublish")) {
3357
- this.hooks
3358
- .runContentAfterUnpublish(content, collection)
3359
- .catch((err) => console.error("EmDash afterUnpublish hook error:", err));
3360
- }
3361
-
3362
- // Sandboxed plugins
3363
- for (const [pluginKey, plugin] of this.sandboxedPlugins) {
3364
- const [pluginId] = pluginKey.split(":");
3365
- if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
3395
+ private runAfterPublishHooks(content: Record<string, unknown>, collection: string): void {
3396
+ this.runDeferredContentHook("content:afterPublish", content, collection);
3397
+ }
3366
3398
 
3367
- plugin
3368
- .invokeHook("content:afterUnpublish", { content, collection })
3369
- .catch((err) =>
3370
- console.error(`EmDash: Sandboxed plugin ${pluginId} afterUnpublish error:`, err),
3371
- );
3372
- }
3399
+ private runAfterUnpublishHooks(content: Record<string, unknown>, collection: string): void {
3400
+ this.runDeferredContentHook("content:afterUnpublish", content, collection);
3373
3401
  }
3374
3402
 
3375
3403
  private runAfterRestoreHooks(content: Record<string, unknown>, collection: string): void {
3376
- after(async () => {
3377
- // Trusted plugins
3378
- if (this.hooks.hasHooks("content:afterRestore")) {
3379
- try {
3380
- await this.hooks.runContentAfterRestore(content, collection);
3381
- } catch (err) {
3382
- console.error("EmDash afterRestore hook error:", err);
3383
- }
3384
- }
3404
+ this.runDeferredContentHook("content:afterRestore", content, collection);
3405
+ }
3385
3406
 
3386
- // Sandboxed plugins
3387
- const tasks: Promise<void>[] = [];
3388
- for (const [pluginKey, plugin] of this.sandboxedPlugins) {
3389
- const [pluginId] = pluginKey.split(":");
3390
- if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
3407
+ private runAfterScheduleHooks(content: Record<string, unknown>, collection: string): void {
3408
+ this.runDeferredContentHook("content:afterSchedule", content, collection);
3409
+ }
3391
3410
 
3392
- tasks.push(
3393
- (async () => {
3394
- try {
3395
- await plugin.invokeHook("content:afterRestore", { content, collection });
3396
- } catch (err) {
3397
- console.error(`EmDash: Sandboxed plugin ${pluginId} afterRestore error:`, err);
3398
- }
3399
- })(),
3400
- );
3401
- }
3402
- await Promise.allSettled(tasks);
3403
- });
3411
+ private runAfterUnscheduleHooks(content: Record<string, unknown>, collection: string): void {
3412
+ this.runDeferredContentHook("content:afterUnschedule", content, collection);
3404
3413
  }
3405
3414
 
3406
3415
  private async handleSandboxedRoute(
package/src/index.ts CHANGED
@@ -255,6 +255,9 @@ export type {
255
255
  ContentHookEvent,
256
256
  ContentDeleteEvent,
257
257
  ContentPublishStateChangeEvent,
258
+ ContentRestoreStateChangeEvent,
259
+ ContentScheduleStateChangeEvent,
260
+ ContentStateChangeEvent,
258
261
  MediaUploadEvent,
259
262
  HookResult,
260
263
  PluginRoute,
package/src/loader.ts CHANGED
@@ -25,15 +25,23 @@ import { isMissingColumnError, isMissingTableError } from "./utils/db-errors.js"
25
25
  const FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
26
26
 
27
27
  /**
28
- * SEO columns joined in from `_emdash_seo` on the single-entry path, mapped to
29
- * aliased result keys. SEO lives in a side table, so a LEFT JOIN folds it into
30
- * the entry load at zero extra query cost; the result is surfaced as a nested
31
- * `data.seo` object (see extractSeo) rather than flat fields.
28
+ * SEO columns folded into the single-entry query as a single JSON column
29
+ * (`_emdash_seo` in the result set), then expanded onto the row under these
30
+ * aliases for `extractSeo()`. Surfacing SEO as one aggregated column keeps the
31
+ * result-set width bounded regardless of how many fields the collection has,
32
+ * which matters for D1: a flat `LEFT JOIN _emdash_seo` adds 5 alias columns to
33
+ * every row and pushes wide collections (common after WordPress / ACF imports)
34
+ * past D1's per-result-set column limit, surfacing as a silent null entry.
35
+ * One JSON column is one column, so the join stays safe at any schema width.
36
+ *
37
+ * The aliases mirror the strategy used by `foldedHydrationSelects` for byline
38
+ * and taxonomy hydration: aggregate in SQL, expand in JS. SEO is 1:1 with
39
+ * content, so the subquery uses `json_object` (not the array aggregator).
32
40
  *
33
41
  * The `_emdash_` prefix on the aliases guarantees they can never collide with
34
42
  * a content field. Field slugs must match `/^[a-z][a-z0-9_]*$/`, so a user can
35
- * legitimately define a `seo_title` field; selecting the joined column under
36
- * its bare name would shadow that field in the result set and drop the user's
43
+ * legitimately define a `seo_title` field; surfacing the SEO column under its
44
+ * bare name would shadow that field in the result set and drop the user's
37
45
  * value. The prefix (illegal as a leading slug char) sidesteps this entirely.
38
46
  */
39
47
  const SEO_COLUMN_ALIASES: Record<string, string> = {
@@ -47,6 +55,9 @@ const SEO_COLUMN_ALIASES: Record<string, string> = {
47
55
  /** Aliased SEO result keys — excluded from generic field mapping. */
48
56
  const SEO_ALIAS_COLUMNS = Object.values(SEO_COLUMN_ALIASES);
49
57
 
58
+ /** Folded SEO JSON column name in the result set (expanded onto aliases in JS). */
59
+ const SEO_FOLDED_COLUMN = "_emdash_seo";
60
+
50
61
  /**
51
62
  * System columns excluded from entry.data
52
63
  * Note: slug is intentionally NOT excluded - it's useful as data.slug in templates
@@ -67,15 +78,17 @@ const SYSTEM_COLUMNS = new Set([
67
78
  "draft_revision_id",
68
79
  "locale",
69
80
  "translation_group",
70
- // Aliased SEO columns joined from _emdash_seo on the single-entry path.
71
- // Surfaced as a nested data.seo object (see extractSeo), never as flat
72
- // fields. The aliases are _emdash_-prefixed so they can't shadow a user
73
- // field named e.g. `seo_title`.
81
+ // Aliased SEO columns expanded from the folded _emdash_seo JSON column on
82
+ // the single-entry path. Surfaced as a nested data.seo object (see
83
+ // extractSeo), never as flat fields. The aliases are _emdash_-prefixed so
84
+ // they can't shadow a user field named e.g. `seo_title`.
74
85
  ...SEO_ALIAS_COLUMNS,
75
- // Folded hydration JSON columns (see foldedHydrationSelects) — surfaced via
76
- // the FOLDED_* markers, never as flat fields.
86
+ // Folded hydration JSON columns (see foldedHydrationSelects and
87
+ // foldedSeoSelect) — surfaced via the FOLDED_* markers or expanded onto
88
+ // SEO_ALIAS_COLUMNS, never as flat fields.
77
89
  "_emdash_terms",
78
90
  "_emdash_bylines",
91
+ SEO_FOLDED_COLUMN,
79
92
  ]);
80
93
 
81
94
  /** Markers for byline/taxonomy hydration folded into the content query. */
@@ -123,6 +136,62 @@ function foldedHydrationSelects(db: Kysely<any>, type: string, outer: string) {
123
136
  return { terms, bylines };
124
137
  }
125
138
 
139
+ /**
140
+ * Correlated JSON-object subquery that folds per-entry SEO into the content
141
+ * query without widening the result set: 1 row of `_emdash_seo` becomes 1 JSON
142
+ * column rather than 5 flat columns. The JSON column is expanded onto the row
143
+ * via {@link expandFoldedSeo} after the query runs, preserving the alias keys
144
+ * that {@link extractSeo} reads. Missing SEO row (no entry in `_emdash_seo`)
145
+ * yields NULL, which {@link expandFoldedSeo} treats as "no SEO" - identical to
146
+ * the prior LEFT JOIN miss behavior.
147
+ *
148
+ * Dialect-specific aggregation mirrors {@link foldedHydrationSelects}: SQLite
149
+ * `json_object` returns a JSON *string*, Postgres `json_build_object` returns
150
+ * parsed JSON; both branches are handled in expansion.
151
+ */
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance
153
+ function foldedSeoSelect(db: Kysely<any>, type: string, outer: string) {
154
+ const o = sql.ref(outer);
155
+ const pg = isPostgres(db);
156
+ // Use raw column names (not aliases) as JSON keys: the JSON is expanded back
157
+ // onto SEO_COLUMN_ALIASES in JS, and keeping the keys matched to the
158
+ // underlying columns makes the SQL readable and the expansion 1-to-1.
159
+ const pairs =
160
+ "'seo_title', s.seo_title, 'seo_description', s.seo_description, 'seo_image', s.seo_image, 'seo_canonical', s.seo_canonical, 'seo_no_index', s.seo_no_index";
161
+ const obj = pg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);
162
+ return sql`(SELECT ${obj} FROM ${sql.ref("_emdash_seo")} AS s WHERE s.collection = ${type} AND s.content_id = ${o}.id LIMIT 1) AS ${sql.ref(SEO_FOLDED_COLUMN)}`;
163
+ }
164
+
165
+ /**
166
+ * Expand the folded `_emdash_seo` JSON column onto the row using SEO_COLUMN_ALIASES,
167
+ * so {@link extractSeo} reads it transparently. SQLite returns a JSON string
168
+ * (parse it); Postgres returns already-parsed JSON. Missing/malformed/null is
169
+ * a no-op: {@link extractSeo} returns null when the aliases are absent.
170
+ */
171
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
172
+ return !!value && typeof value === "object" && !Array.isArray(value);
173
+ }
174
+
175
+ function expandFoldedSeo(row: Record<string, unknown>): void {
176
+ const raw = row[SEO_FOLDED_COLUMN];
177
+ delete row[SEO_FOLDED_COLUMN];
178
+ let parsed: Record<string, unknown> | null = null;
179
+ if (typeof raw === "string") {
180
+ try {
181
+ const candidate: unknown = JSON.parse(raw);
182
+ if (isPlainObject(candidate)) parsed = candidate;
183
+ } catch {
184
+ return; // malformed JSON: leave the row without SEO aliases (extractSeo returns null)
185
+ }
186
+ } else if (isPlainObject(raw)) {
187
+ parsed = raw;
188
+ }
189
+ if (!parsed) return;
190
+ for (const [col, alias] of Object.entries(SEO_COLUMN_ALIASES)) {
191
+ row[alias] = parsed[col] ?? null;
192
+ }
193
+ }
194
+
126
195
  /**
127
196
  * Stash folded hydration JSON (non-enumerable) for the query.ts fast paths.
128
197
  * SQLite returns a JSON string (parse it); Postgres returns already-parsed JSON.
@@ -227,6 +296,18 @@ async function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {
227
296
  }
228
297
  }
229
298
 
299
+ /**
300
+ * Reset the module-scoped taxonomy-names cache.
301
+ *
302
+ * Called from `invalidateTaxonomyDefsCache()` so that creating or seeding a
303
+ * taxonomy definition is reflected within the current isolate instead of
304
+ * waiting for the isolate to recycle. Keeps this cache consistent with the
305
+ * isolate-wide taxonomy-defs cache in `taxonomies/index.ts`.
306
+ */
307
+ export function resetTaxonomyNamesCache(): void {
308
+ taxonomyNames = null;
309
+ }
310
+
230
311
  /**
231
312
  * System columns to include in data (mapped to camelCase where needed)
232
313
  */
@@ -879,11 +960,12 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
879
960
  taxonomyFilters.map(
880
961
  (f) => sql`AND EXISTS (
881
962
  SELECT 1 FROM content_taxonomies ct
882
- INNER JOIN taxonomies t ON t.id = ct.taxonomy_id
963
+ INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id
883
964
  WHERE ct.collection = ${type}
884
965
  AND ct.entry_id = ${sql.ref(tableName)}.id
885
966
  AND t.name = ${f.name}
886
967
  AND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})
968
+ ${locale ? sql`AND t.locale = ${locale}` : sql``}
887
969
  )`,
888
970
  ),
889
971
  sql` `,
@@ -1050,31 +1132,25 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1050
1132
  // When locale is specified, prefer locale-scoped slug match,
1051
1133
  // but IDs are globally unique so always check id without locale scope.
1052
1134
  //
1053
- // LEFT JOIN _emdash_seo folds per-entry SEO (canonical, noindex,
1054
- // etc.) into this single query at zero extra round-trip cost. The
1055
- // joined columns are surfaced as a nested data.seo object via
1056
- // extractSeo() and excluded from the generic field mapping. SEO is
1057
- // 1:1 with content (PK on collection+content_id), so the join never
1058
- // multiplies rows.
1059
- const seoSelect = sql.join(
1060
- Object.entries(SEO_COLUMN_ALIASES).map(
1061
- ([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,
1062
- ),
1063
- );
1064
- // Fold byline + taxonomy hydration into the content query (see
1065
- // foldedHydrationSelects), removing the two separate hydration
1066
- // round trips per fetch.
1135
+ // Byline + taxonomy hydration (foldedHydrationSelects) and per-entry
1136
+ // SEO (foldedSeoSelect) are each surfaced as a single aggregated JSON
1137
+ // column rather than flat columns. This keeps the result-set width
1138
+ // bounded at any collection schema width: a flat `LEFT JOIN _emdash_seo`
1139
+ // adds 5 alias columns to every row and pushes wide flat-schema
1140
+ // collections (common after WordPress / ACF imports) past D1's
1141
+ // per-result-set column limit, surfacing as a silent null entry. One
1142
+ // JSON column is one column, so the join stays safe at any width and
1143
+ // we keep the single round trip.
1067
1144
  const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
1068
1145
  db,
1069
1146
  type,
1070
1147
  "c",
1071
1148
  );
1149
+ const seoSelect = foldedSeoSelect(db, type, "c");
1072
1150
  const result = locale
1073
1151
  ? await sql<Record<string, unknown>>`
1074
1152
  SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
1075
1153
  FROM ${sql.ref(tableName)} AS c
1076
- LEFT JOIN ${sql.ref("_emdash_seo")} AS s
1077
- ON s.collection = ${type} AND s.content_id = c.id
1078
1154
  WHERE c.deleted_at IS NULL
1079
1155
  AND ((c.slug = ${id} AND c.locale = ${locale}) OR c.id = ${id})
1080
1156
  LIMIT 1
@@ -1082,8 +1158,6 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1082
1158
  : await sql<Record<string, unknown>>`
1083
1159
  SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
1084
1160
  FROM ${sql.ref(tableName)} AS c
1085
- LEFT JOIN ${sql.ref("_emdash_seo")} AS s
1086
- ON s.collection = ${type} AND s.content_id = c.id
1087
1161
  WHERE c.deleted_at IS NULL
1088
1162
  AND (c.slug = ${id} OR c.id = ${id})
1089
1163
  LIMIT 1
@@ -1094,6 +1168,11 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
1094
1168
  return undefined;
1095
1169
  }
1096
1170
 
1171
+ // Expand the folded SEO JSON column onto SEO_COLUMN_ALIASES so
1172
+ // extractSeo() reads it transparently. Missing/null SEO is a
1173
+ // no-op: extractSeo() returns null when the aliases are absent.
1174
+ expandFoldedSeo(row);
1175
+
1097
1176
  const i18nConfig = virtualConfig?.i18n;
1098
1177
  const i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;
1099
1178
  const entrySlug = rowStr(row, "slug") || rowStr(row, "id");
@@ -0,0 +1,302 @@
1
+ import { normalizeMime } from "../mime.js";
2
+ import type {
3
+ ExtractedMediaUsageOccurrence,
4
+ ExtractMediaUsageOccurrencesInput,
5
+ MediaKind,
6
+ MediaUsageExtractionSubField,
7
+ MediaUsageReferenceType,
8
+ } from "./types.js";
9
+
10
+ const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
11
+ const URL_LIKE_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
12
+
13
+ interface MediaRef {
14
+ mediaId: string | null;
15
+ provider: string;
16
+ providerAssetId: string;
17
+ mediaKind: MediaKind | null;
18
+ mimeType: string | null;
19
+ }
20
+
21
+ interface AddOccurrenceInput {
22
+ fieldSlug: string;
23
+ fieldPath: string;
24
+ referenceType: MediaUsageReferenceType;
25
+ value: unknown;
26
+ fallbackKind: MediaKind | null;
27
+ }
28
+
29
+ export function extractMediaUsageOccurrences({
30
+ fields,
31
+ data,
32
+ }: ExtractMediaUsageOccurrencesInput): ExtractedMediaUsageOccurrence[] {
33
+ const occurrences: ExtractedMediaUsageOccurrence[] = [];
34
+ const seen = new Set<string>();
35
+
36
+ for (const field of fields) {
37
+ const value = data[field.slug];
38
+
39
+ if (field.type === "image") {
40
+ addOccurrence(occurrences, seen, {
41
+ fieldSlug: field.slug,
42
+ fieldPath: field.slug,
43
+ referenceType: "image_field",
44
+ value,
45
+ fallbackKind: "image",
46
+ });
47
+ continue;
48
+ }
49
+
50
+ if (field.type === "file") {
51
+ addOccurrence(occurrences, seen, {
52
+ fieldSlug: field.slug,
53
+ fieldPath: field.slug,
54
+ referenceType: "file_field",
55
+ value,
56
+ fallbackKind: null,
57
+ });
58
+ continue;
59
+ }
60
+
61
+ if (field.type === "repeater") {
62
+ extractRepeaterOccurrences(occurrences, seen, field.slug, value, field.validation?.subFields);
63
+ continue;
64
+ }
65
+
66
+ if (field.type === "portableText") {
67
+ extractPortableTextOccurrences(occurrences, seen, field.slug, value);
68
+ }
69
+ }
70
+
71
+ return occurrences;
72
+ }
73
+
74
+ function extractRepeaterOccurrences(
75
+ occurrences: ExtractedMediaUsageOccurrence[],
76
+ seen: Set<string>,
77
+ fieldSlug: string,
78
+ value: unknown,
79
+ subFields: readonly MediaUsageExtractionSubField[] | undefined,
80
+ ): void {
81
+ if (!Array.isArray(value) || !Array.isArray(subFields)) return;
82
+
83
+ for (const [itemIndex, item] of value.entries()) {
84
+ if (!isRecord(item)) continue;
85
+
86
+ for (const subField of subFields) {
87
+ if (subField.type !== "image" && subField.type !== "file") continue;
88
+
89
+ addOccurrence(occurrences, seen, {
90
+ fieldSlug,
91
+ fieldPath: `${fieldSlug}[${itemIndex}].${subField.slug}`,
92
+ referenceType: subField.type === "image" ? "image_field" : "file_field",
93
+ value: item[subField.slug],
94
+ fallbackKind: subField.type === "image" ? "image" : null,
95
+ });
96
+ }
97
+ }
98
+ }
99
+
100
+ function extractPortableTextOccurrences(
101
+ occurrences: ExtractedMediaUsageOccurrence[],
102
+ seen: Set<string>,
103
+ fieldSlug: string,
104
+ value: unknown,
105
+ ): void {
106
+ if (!Array.isArray(value)) return;
107
+
108
+ for (const [blockIndex, block] of value.entries()) {
109
+ if (!isRecord(block) || block._type !== "image" || !isRecord(block.asset)) continue;
110
+
111
+ const provider = normalizeProvider(block.asset.provider);
112
+ const ref = readPortableTextAssetRef(block.asset, provider);
113
+ if (!ref) continue;
114
+
115
+ addRefOccurrence(occurrences, seen, {
116
+ fieldSlug,
117
+ fieldPath: `${fieldSlug}[${blockIndex}].asset.${ref.key}`,
118
+ referenceType: "portable_text_image",
119
+ ref: buildMediaRef({
120
+ id: ref.id,
121
+ provider,
122
+ mimeType: normalizeMimeValue(block.asset.mimeType),
123
+ fallbackKind: "image",
124
+ }),
125
+ });
126
+ }
127
+ }
128
+
129
+ function addOccurrence(
130
+ occurrences: ExtractedMediaUsageOccurrence[],
131
+ seen: Set<string>,
132
+ input: AddOccurrenceInput,
133
+ ): void {
134
+ const ref = readMediaRef(input.value, input.fallbackKind);
135
+ if (!ref) return;
136
+
137
+ addRefOccurrence(occurrences, seen, {
138
+ fieldSlug: input.fieldSlug,
139
+ fieldPath: input.fieldPath,
140
+ referenceType: input.referenceType,
141
+ ref,
142
+ });
143
+ }
144
+
145
+ function addRefOccurrence(
146
+ occurrences: ExtractedMediaUsageOccurrence[],
147
+ seen: Set<string>,
148
+ input: {
149
+ fieldSlug: string;
150
+ fieldPath: string;
151
+ referenceType: MediaUsageReferenceType;
152
+ ref: MediaRef | null;
153
+ },
154
+ ): void {
155
+ if (!input.ref) return;
156
+
157
+ const occurrence: ExtractedMediaUsageOccurrence = {
158
+ fieldSlug: input.fieldSlug,
159
+ fieldPath: input.fieldPath,
160
+ occurrenceIndex: 0,
161
+ referenceType: input.referenceType,
162
+ mediaId: input.ref.mediaId,
163
+ provider: input.ref.provider,
164
+ providerAssetId: input.ref.providerAssetId,
165
+ mediaKind: input.ref.mediaKind,
166
+ mimeType: input.ref.mimeType,
167
+ };
168
+
169
+ const key = [
170
+ occurrence.fieldSlug,
171
+ occurrence.fieldPath,
172
+ occurrence.occurrenceIndex,
173
+ occurrence.referenceType,
174
+ occurrence.provider,
175
+ occurrence.providerAssetId,
176
+ occurrence.mediaId ?? "",
177
+ ].join("\0");
178
+
179
+ if (seen.has(key)) return;
180
+ seen.add(key);
181
+ occurrences.push(occurrence);
182
+ }
183
+
184
+ function readMediaRef(value: unknown, fallbackKind: MediaKind | null): MediaRef | null {
185
+ if (typeof value === "string") {
186
+ const id = normalizeLocalMediaId(value);
187
+ return id ? buildMediaRef({ id, provider: "local", mimeType: null, fallbackKind }) : null;
188
+ }
189
+
190
+ if (!isRecord(value)) return null;
191
+
192
+ const provider = normalizeProvider(value.provider);
193
+ const id = provider === "local" ? normalizeLocalMediaId(value.id) : normalizeStableId(value.id);
194
+ if (!id) return null;
195
+
196
+ return buildMediaRef({
197
+ id,
198
+ provider,
199
+ mimeType: normalizeMimeValue(value.mimeType),
200
+ fallbackKind,
201
+ });
202
+ }
203
+
204
+ function buildMediaRef(input: {
205
+ id: string;
206
+ provider: string;
207
+ mimeType: string | null;
208
+ fallbackKind: MediaKind | null;
209
+ }): MediaRef | null {
210
+ const provider = normalizeProvider(input.provider);
211
+ if (provider === "external") return null;
212
+
213
+ return {
214
+ mediaId: provider === "local" ? input.id : null,
215
+ provider,
216
+ providerAssetId: input.id,
217
+ mediaKind: mediaKindFromMime(input.mimeType) ?? input.fallbackKind,
218
+ mimeType: input.mimeType,
219
+ };
220
+ }
221
+
222
+ function readPortableTextAssetRef(
223
+ asset: Record<string, unknown>,
224
+ provider: string,
225
+ ): { key: "_ref" | "id"; id: string } | null {
226
+ const normalizeId = provider === "local" ? normalizeLocalMediaId : normalizeStableId;
227
+ const ref = normalizeId(asset._ref);
228
+ if (ref) return { key: "_ref", id: ref };
229
+
230
+ const id = normalizeId(asset.id);
231
+ if (id) return { key: "id", id };
232
+
233
+ return null;
234
+ }
235
+
236
+ function normalizeProvider(value: unknown): string {
237
+ const provider = readString(value)?.trim();
238
+ return provider || "local";
239
+ }
240
+
241
+ function normalizeLocalMediaId(value: unknown): string | null {
242
+ const id = normalizeStableId(value);
243
+ if (!id) return null;
244
+ return id.includes("/") ? null : id;
245
+ }
246
+
247
+ function normalizeStableId(value: unknown): string | null {
248
+ if (typeof value !== "string") return null;
249
+ const trimmed = value.trim();
250
+ if (!trimmed) return null;
251
+ if (URL_LIKE_RE.test(trimmed)) return null;
252
+ if (trimmed.startsWith(INTERNAL_MEDIA_PREFIX)) return null;
253
+ return trimmed;
254
+ }
255
+
256
+ function normalizeMimeValue(value: unknown): string | null {
257
+ if (typeof value !== "string") return null;
258
+ const normalized = normalizeMime(value);
259
+ return normalized.includes("/") ? normalized : null;
260
+ }
261
+
262
+ function mediaKindFromMime(mimeType: string | null): MediaKind | null {
263
+ if (!mimeType) return null;
264
+ if (mimeType.startsWith("image/")) return "image";
265
+ if (mimeType.startsWith("video/")) return "video";
266
+ if (mimeType.startsWith("audio/")) return "audio";
267
+ if (mimeType.startsWith("font/") || mimeType.startsWith("application/font-")) return "font";
268
+ if (mimeType.startsWith("text/")) return "text";
269
+ if (isDocumentMime(mimeType)) return "document";
270
+ if (isArchiveMime(mimeType)) return "archive";
271
+ return "other";
272
+ }
273
+
274
+ function isDocumentMime(mimeType: string): boolean {
275
+ return (
276
+ mimeType === "application/pdf" ||
277
+ mimeType === "application/msword" ||
278
+ mimeType === "application/rtf" ||
279
+ mimeType === "application/vnd.ms-excel" ||
280
+ mimeType === "application/vnd.ms-powerpoint" ||
281
+ mimeType.startsWith("application/vnd.openxmlformats-officedocument.")
282
+ );
283
+ }
284
+
285
+ function isArchiveMime(mimeType: string): boolean {
286
+ return (
287
+ mimeType === "application/zip" ||
288
+ mimeType === "application/gzip" ||
289
+ mimeType === "application/x-tar" ||
290
+ mimeType === "application/x-7z-compressed" ||
291
+ mimeType === "application/x-rar-compressed" ||
292
+ mimeType === "application/vnd.rar"
293
+ );
294
+ }
295
+
296
+ function readString(value: unknown): string | null {
297
+ return typeof value === "string" ? value : null;
298
+ }
299
+
300
+ function isRecord(value: unknown): value is Record<string, unknown> {
301
+ return typeof value === "object" && value !== null && !Array.isArray(value);
302
+ }