emdash 0.20.0 → 0.21.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 (547) hide show
  1. package/dist/{adapters-BzIHV3sw.d.mts → adapters-BxSmgtbF.d.mts} +1 -1
  2. package/dist/{adapters-BzIHV3sw.d.mts.map → adapters-BxSmgtbF.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-B1u7Qnvg.mjs → allowed-origins-BqC8cul8.mjs} +2 -2
  4. package/dist/{allowed-origins-B1u7Qnvg.mjs.map → allowed-origins-BqC8cul8.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +13 -12
  7. package/dist/api/route-utils.mjs.map +1 -1
  8. package/dist/api/schemas/index.d.mts +1 -1
  9. package/dist/api/schemas/index.mjs +3 -2
  10. package/dist/{api-DStv36ik.mjs → api-DxjIV2o8.mjs} +13 -13
  11. package/dist/{api-DStv36ik.mjs.map → api-DxjIV2o8.mjs.map} +1 -1
  12. package/dist/{api-tokens-DPfhPu5V.mjs → api-tokens-BFFkB0jB.mjs} +2 -2
  13. package/dist/{api-tokens-DPfhPu5V.mjs.map → api-tokens-BFFkB0jB.mjs.map} +1 -1
  14. package/dist/{apply-Dr7snAMT.mjs → apply-CLjxheyb.mjs} +12 -12
  15. package/dist/{apply-Dr7snAMT.mjs.map → apply-CLjxheyb.mjs.map} +1 -1
  16. package/dist/astro/index.d.mts +10 -10
  17. package/dist/astro/index.d.mts.map +1 -1
  18. package/dist/astro/index.mjs +50 -15
  19. package/dist/astro/index.mjs.map +1 -1
  20. package/dist/astro/middleware/auth.d.mts +9 -9
  21. package/dist/astro/middleware/auth.mjs +5 -5
  22. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  23. package/dist/astro/middleware/redirect.mjs +11 -2
  24. package/dist/astro/middleware/redirect.mjs.map +1 -1
  25. package/dist/astro/middleware/request-context.mjs +3 -2
  26. package/dist/astro/middleware/request-context.mjs.map +1 -1
  27. package/dist/astro/middleware/setup.mjs +1 -1
  28. package/dist/astro/middleware.d.mts +1 -1
  29. package/dist/astro/middleware.mjs +63 -60
  30. package/dist/astro/middleware.mjs.map +1 -1
  31. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -4
  32. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  33. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -4
  34. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  35. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  36. package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
  37. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -7
  39. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  40. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -7
  41. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  42. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -7
  43. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  44. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -12
  45. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  46. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -12
  47. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  48. package/dist/astro/routes/api/admin/bylines/index.mjs +14 -12
  49. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  50. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +9 -8
  51. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  52. package/dist/astro/routes/api/admin/comments/_id_.mjs +3 -3
  53. package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -6
  54. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/comments/counts.mjs +3 -3
  56. package/dist/astro/routes/api/admin/comments/index.mjs +7 -6
  57. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
  59. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
  60. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  61. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  62. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +29 -27
  63. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +29 -27
  65. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -26
  67. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  68. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -26
  69. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -26
  71. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  72. package/dist/astro/routes/api/admin/plugins/index.mjs +28 -26
  73. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  75. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -26
  76. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -26
  78. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -26
  80. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -26
  82. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -27
  84. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  85. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -26
  86. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -27
  88. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -26
  90. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  91. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -26
  92. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  94. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -26
  95. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
  97. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  98. package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -4
  99. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  101. package/dist/astro/routes/api/admin/users/index.mjs +5 -4
  102. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  103. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  104. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  105. package/dist/astro/routes/api/auth/invite/complete.mjs +9 -8
  106. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  107. package/dist/astro/routes/api/auth/invite/index.mjs +6 -5
  108. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  109. package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -7
  110. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  111. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  112. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -7
  113. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  114. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  115. package/dist/astro/routes/api/auth/me.mjs +5 -4
  116. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  117. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  118. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  119. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  120. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -4
  121. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  122. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  123. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -9
  124. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  125. package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -7
  126. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  127. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -8
  128. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  129. package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -8
  130. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  131. package/dist/astro/routes/api/auth/signup/complete.mjs +9 -8
  132. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  133. package/dist/astro/routes/api/auth/signup/request.mjs +8 -7
  134. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  135. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  136. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -9
  137. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  138. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  139. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
  140. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
  141. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  142. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -8
  143. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  144. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -5
  145. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  146. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
  147. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  148. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -5
  149. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  150. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +10 -9
  151. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  152. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  153. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
  154. package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -5
  155. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  156. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  157. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -5
  158. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  159. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -5
  160. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  161. package/dist/astro/routes/api/dashboard.mjs +3 -3
  162. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  163. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  164. package/dist/astro/routes/api/import/probe.mjs +10 -9
  165. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  166. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  167. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  168. package/dist/astro/routes/api/import/wordpress/execute.mjs +10 -9
  169. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  170. package/dist/astro/routes/api/import/wordpress/media.mjs +8 -7
  171. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  172. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -8
  173. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  174. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -7
  175. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  176. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  177. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -9
  178. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  179. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  180. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +14 -12
  181. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  182. package/dist/astro/routes/api/manifest.mjs +3 -3
  183. package/dist/astro/routes/api/mcp.mjs +20 -19
  184. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  185. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -5
  186. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  187. package/dist/astro/routes/api/media/_id_.mjs +6 -5
  188. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  189. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  190. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  191. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  192. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  193. package/dist/astro/routes/api/media/upload-url.mjs +8 -7
  194. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  195. package/dist/astro/routes/api/media.mjs +10 -9
  196. package/dist/astro/routes/api/media.mjs.map +1 -1
  197. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -5
  198. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  199. package/dist/astro/routes/api/menus/_name_/items.mjs +6 -5
  200. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  201. package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -5
  202. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  203. package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -5
  204. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  205. package/dist/astro/routes/api/menus/_name_.mjs +6 -5
  206. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  207. package/dist/astro/routes/api/menus/index.mjs +6 -5
  208. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  209. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  210. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  211. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  212. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  213. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  214. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  215. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  216. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  217. package/dist/astro/routes/api/openapi.json.mjs +3 -2
  218. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  219. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  220. package/dist/astro/routes/api/redirects/404s/index.mjs +7 -6
  221. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  222. package/dist/astro/routes/api/redirects/404s/summary.mjs +7 -6
  223. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  224. package/dist/astro/routes/api/redirects/_id_.mjs +8 -7
  225. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  226. package/dist/astro/routes/api/redirects/index.mjs +8 -7
  227. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  228. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  229. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
  230. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -26
  231. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  232. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -26
  233. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  234. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -26
  235. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  236. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -26
  237. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  238. package/dist/astro/routes/api/schema/collections/index.mjs +28 -26
  239. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  240. package/dist/astro/routes/api/schema/index.mjs +5 -5
  241. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -26
  242. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  243. package/dist/astro/routes/api/schema/orphans/index.mjs +28 -26
  244. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  245. package/dist/astro/routes/api/search/enable.mjs +9 -8
  246. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  247. package/dist/astro/routes/api/search/index.mjs +8 -7
  248. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  249. package/dist/astro/routes/api/search/rebuild.mjs +9 -8
  250. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  251. package/dist/astro/routes/api/search/stats.mjs +5 -5
  252. package/dist/astro/routes/api/search/suggest.mjs +8 -7
  253. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  254. package/dist/astro/routes/api/sections/_slug_.mjs +8 -7
  255. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  256. package/dist/astro/routes/api/sections/index.mjs +8 -7
  257. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  258. package/dist/astro/routes/api/settings/email.mjs +3 -3
  259. package/dist/astro/routes/api/settings.mjs +11 -9
  260. package/dist/astro/routes/api/settings.mjs.map +1 -1
  261. package/dist/astro/routes/api/setup/admin-verify.mjs +10 -9
  262. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  263. package/dist/astro/routes/api/setup/admin.mjs +9 -8
  264. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  265. package/dist/astro/routes/api/setup/dev-bypass.mjs +19 -18
  266. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  267. package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
  268. package/dist/astro/routes/api/setup/index.mjs +20 -18
  269. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  270. package/dist/astro/routes/api/setup/status.mjs +3 -3
  271. package/dist/astro/routes/api/snapshot.mjs +5 -4
  272. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  273. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -10
  274. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  275. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -10
  276. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  277. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -10
  278. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  279. package/dist/astro/routes/api/taxonomies/index.mjs +11 -10
  280. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  281. package/dist/astro/routes/api/themes/preview.mjs +5 -4
  282. package/dist/astro/routes/api/themes/preview.mjs.map +1 -1
  283. package/dist/astro/routes/api/typegen.mjs +4 -4
  284. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  285. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  286. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  287. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -5
  288. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  289. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
  290. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  291. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
  292. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  293. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  294. package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
  295. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  296. package/dist/astro/routes/api/widget-components.mjs +2 -2
  297. package/dist/astro/routes/robots.txt.mjs +5 -4
  298. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  299. package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -7
  300. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  301. package/dist/astro/routes/sitemap.xml.mjs +6 -5
  302. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  303. package/dist/astro/types.d.mts +12 -12
  304. package/dist/auth/providers/github.d.mts +1 -1
  305. package/dist/auth/providers/google.d.mts +1 -1
  306. package/dist/{authorize-DsMSVSaY.mjs → authorize-D5gfBVU5.mjs} +2 -2
  307. package/dist/{authorize-DsMSVSaY.mjs.map → authorize-D5gfBVU5.mjs.map} +1 -1
  308. package/dist/{byline-DUx48sJp.mjs → byline-V_Qp1Ziw.mjs} +27 -14
  309. package/dist/byline-V_Qp1Ziw.mjs.map +1 -0
  310. package/dist/{byline-fields-8TMtkBnH.mjs → byline-fields-B0NO1yUB.mjs} +3 -3
  311. package/dist/{byline-fields-8TMtkBnH.mjs.map → byline-fields-B0NO1yUB.mjs.map} +1 -1
  312. package/dist/{byline-fields-DbibsvTl.d.mts → byline-fields-CQJRIQkn.d.mts} +32 -32
  313. package/dist/{byline-fields-DbibsvTl.d.mts.map → byline-fields-CQJRIQkn.d.mts.map} +1 -1
  314. package/dist/{byline-fields--WxSNS79.mjs → byline-fields-nBVqK_Ff.mjs} +2 -2
  315. package/dist/{byline-fields--WxSNS79.mjs.map → byline-fields-nBVqK_Ff.mjs.map} +1 -1
  316. package/dist/{byline-registry-CWP7I71B.mjs → byline-registry-DedidtqC.mjs} +2 -2
  317. package/dist/{byline-registry-CWP7I71B.mjs.map → byline-registry-DedidtqC.mjs.map} +1 -1
  318. package/dist/{bylines-BdxWCnPL.mjs → bylines-B2NWnIwS.mjs} +2 -2
  319. package/dist/{bylines-BdxWCnPL.mjs.map → bylines-B2NWnIwS.mjs.map} +1 -1
  320. package/dist/{bylines-s8c2DXbH.mjs → bylines-DfGDnred.mjs} +7 -7
  321. package/dist/{bylines-s8c2DXbH.mjs.map → bylines-DfGDnred.mjs.map} +1 -1
  322. package/dist/{cache-B_HzASVT.mjs → cache-DTTHWD8n.mjs} +1 -1
  323. package/dist/{cache-B_HzASVT.mjs.map → cache-DTTHWD8n.mjs.map} +1 -1
  324. package/dist/{challenge-store-DXX3rfdI.mjs → challenge-store-woE0bbCf.mjs} +1 -1
  325. package/dist/{challenge-store-DXX3rfdI.mjs.map → challenge-store-woE0bbCf.mjs.map} +1 -1
  326. package/dist/cli/index.mjs +19 -18
  327. package/dist/cli/index.mjs.map +1 -1
  328. package/dist/client/cf-access.d.mts +1 -1
  329. package/dist/client/index.d.mts +1 -1
  330. package/dist/client/index.mjs +1 -1
  331. package/dist/{comments-Vkivawyl.mjs → comments-D2hNuxNa.mjs} +1 -1
  332. package/dist/{comments-Vkivawyl.mjs.map → comments-D2hNuxNa.mjs.map} +1 -1
  333. package/dist/{components-CK0cuUoH.mjs → components-DYKp2gmo.mjs} +1 -1
  334. package/dist/{components-CK0cuUoH.mjs.map → components-DYKp2gmo.mjs.map} +1 -1
  335. package/dist/{context-Y7BRkWes.mjs → context-Cm4pt1Ws.mjs} +5 -5
  336. package/dist/{context-Y7BRkWes.mjs.map → context-Cm4pt1Ws.mjs.map} +1 -1
  337. package/dist/{cron-BJ2ClIlj.mjs → cron-DdEVrQ2Y.mjs} +1 -1
  338. package/dist/{cron-BJ2ClIlj.mjs.map → cron-DdEVrQ2Y.mjs.map} +1 -1
  339. package/dist/{dashboard-2JgAMWxK.mjs → dashboard-C-UYpps0.mjs} +1 -1
  340. package/dist/{dashboard-2JgAMWxK.mjs.map → dashboard-C-UYpps0.mjs.map} +1 -1
  341. package/dist/db/index.d.mts +3 -3
  342. package/dist/db/libsql.d.mts +1 -1
  343. package/dist/db/postgres.d.mts +1 -1
  344. package/dist/db/sqlite.d.mts +1 -1
  345. package/dist/{db-errors-CtzxKBxe.mjs → db-errors-BluWkwGI.mjs} +1 -1
  346. package/dist/{db-errors-CtzxKBxe.mjs.map → db-errors-BluWkwGI.mjs.map} +1 -1
  347. package/dist/{default-IlBaTFxM.mjs → default-NHGuJzQ3.mjs} +1 -1
  348. package/dist/{default-IlBaTFxM.mjs.map → default-NHGuJzQ3.mjs.map} +1 -1
  349. package/dist/{device-flow-R23SIbQ2.mjs → device-flow-BQApWgnW.mjs} +4 -4
  350. package/dist/{device-flow-R23SIbQ2.mjs.map → device-flow-BQApWgnW.mjs.map} +1 -1
  351. package/dist/{email-console-DHT2Fbpj.mjs → email-console-BbU3RbWv.mjs} +1 -1
  352. package/dist/{email-console-DHT2Fbpj.mjs.map → email-console-BbU3RbWv.mjs.map} +1 -1
  353. package/dist/{error-RwM4dD35.mjs → error-CNn_w7jf.mjs} +1 -1
  354. package/dist/{error-RwM4dD35.mjs.map → error-CNn_w7jf.mjs.map} +1 -1
  355. package/dist/{escape-Ds07EEyu.mjs → escape-DPgcxcpL.mjs} +1 -1
  356. package/dist/{escape-Ds07EEyu.mjs.map → escape-DPgcxcpL.mjs.map} +1 -1
  357. package/dist/{fts-manager-1RgHmopc.mjs → fts-manager-Cx5z8jdA.mjs} +1 -1
  358. package/dist/{fts-manager-1RgHmopc.mjs.map → fts-manager-Cx5z8jdA.mjs.map} +1 -1
  359. package/dist/{hash-9w3pd3-m.mjs → hash-DlvIFn0b.mjs} +1 -1
  360. package/dist/{hash-9w3pd3-m.mjs.map → hash-DlvIFn0b.mjs.map} +1 -1
  361. package/dist/{import-Dh8bWmyq.mjs → import-KyxT1Mbs.mjs} +3 -3
  362. package/dist/{import-Dh8bWmyq.mjs.map → import-KyxT1Mbs.mjs.map} +1 -1
  363. package/dist/{index-B1keaX5Y.d.mts → index-D2VAiumu.d.mts} +15 -15
  364. package/dist/{index-B1keaX5Y.d.mts.map → index-D2VAiumu.d.mts.map} +1 -1
  365. package/dist/{index-DR56od45.d.mts → index-uT2yR66F.d.mts} +3 -3
  366. package/dist/{index-DR56od45.d.mts.map → index-uT2yR66F.d.mts.map} +1 -1
  367. package/dist/index.d.mts +16 -16
  368. package/dist/index.mjs +48 -46
  369. package/dist/init-lock-DlBHjf9-.mjs +83 -0
  370. package/dist/init-lock-DlBHjf9-.mjs.map +1 -0
  371. package/dist/{load-BBetCvLC.mjs → load-Dq91b_DK.mjs} +1 -1
  372. package/dist/{load-BBetCvLC.mjs.map → load-Dq91b_DK.mjs.map} +1 -1
  373. package/dist/{loader-ZN1ll-d-.mjs → loader-BqWjcH3h.mjs} +2 -2
  374. package/dist/{loader-ZN1ll-d-.mjs.map → loader-BqWjcH3h.mjs.map} +1 -1
  375. package/dist/{manifest-schema-BtwbL_vj.mjs → manifest-schema-DFPeqMAn.mjs} +1 -1
  376. package/dist/{manifest-schema-BtwbL_vj.mjs.map → manifest-schema-DFPeqMAn.mjs.map} +1 -1
  377. package/dist/media/index.d.mts +1 -1
  378. package/dist/media/index.mjs +2 -2
  379. package/dist/media/local-runtime.d.mts +11 -11
  380. package/dist/media/local-runtime.mjs +4 -3
  381. package/dist/media/local-runtime.mjs.map +1 -1
  382. package/dist/{media-allowlist-Dknq-OFY.mjs → media-allowlist-_A0SuDn4.mjs} +2 -2
  383. package/dist/{media-allowlist-Dknq-OFY.mjs.map → media-allowlist-_A0SuDn4.mjs.map} +1 -1
  384. package/dist/{media-url-VClf8glU.mjs → media-url-CqLd69IO.mjs} +1 -1
  385. package/dist/{media-url-VClf8glU.mjs.map → media-url-CqLd69IO.mjs.map} +1 -1
  386. package/dist/{menus-DrQLusqj.mjs → menus-Ryk9L7fT.mjs} +9 -9
  387. package/dist/{menus-DrQLusqj.mjs.map → menus-Ryk9L7fT.mjs.map} +1 -1
  388. package/dist/{mime-CCEzze7W.mjs → mime-YbtlEtvS.mjs} +1 -1
  389. package/dist/{mime-CCEzze7W.mjs.map → mime-YbtlEtvS.mjs.map} +1 -1
  390. package/dist/{mode-CO2vQHfq.mjs → mode-CGXzIbD8.mjs} +1 -1
  391. package/dist/{mode-CO2vQHfq.mjs.map → mode-CGXzIbD8.mjs.map} +1 -1
  392. package/dist/{normalize-CK5o04zr.mjs → normalize-DKsg36ty.mjs} +1 -1
  393. package/dist/{normalize-CK5o04zr.mjs.map → normalize-DKsg36ty.mjs.map} +1 -1
  394. package/dist/{oauth-authorization-Bw4NdF_S.mjs → oauth-authorization-C2kVyjXI.mjs} +4 -4
  395. package/dist/{oauth-authorization-Bw4NdF_S.mjs.map → oauth-authorization-C2kVyjXI.mjs.map} +1 -1
  396. package/dist/{oauth-clients-BGGFp57s.mjs → oauth-clients-BC873NCV.mjs} +1 -1
  397. package/dist/{oauth-clients-BGGFp57s.mjs.map → oauth-clients-BC873NCV.mjs.map} +1 -1
  398. package/dist/{oauth-state-store-97x0xtN2.mjs → oauth-state-store-Cd--TUaq.mjs} +1 -1
  399. package/dist/{oauth-state-store-97x0xtN2.mjs.map → oauth-state-store-Cd--TUaq.mjs.map} +1 -1
  400. package/dist/{oauth-user-lookup-B_vnZHKO.mjs → oauth-user-lookup-e4wOvDud.mjs} +1 -1
  401. package/dist/{oauth-user-lookup-B_vnZHKO.mjs.map → oauth-user-lookup-e4wOvDud.mjs.map} +1 -1
  402. package/dist/{options-DyYIYpPd.d.mts → options-9kLgkE8m.d.mts} +3 -3
  403. package/dist/{options-DyYIYpPd.d.mts.map → options-9kLgkE8m.d.mts.map} +1 -1
  404. package/dist/page/index.d.mts +2 -2
  405. package/dist/{parse-CrGndy1A.mjs → parse-DzSrk1t8.mjs} +2 -2
  406. package/dist/{parse-CrGndy1A.mjs.map → parse-DzSrk1t8.mjs.map} +1 -1
  407. package/dist/{passkey-config-C3QgnQnU.mjs → passkey-config-BpjbE_Uv.mjs} +1 -1
  408. package/dist/{passkey-config-C3QgnQnU.mjs.map → passkey-config-BpjbE_Uv.mjs.map} +1 -1
  409. package/dist/{placeholder-BZxr8W1j.mjs → placeholder-2xumZh4g.mjs} +1 -1
  410. package/dist/{placeholder-BZxr8W1j.mjs.map → placeholder-2xumZh4g.mjs.map} +1 -1
  411. package/dist/{placeholder-CVBv5z8k.d.mts → placeholder-BevVKfay.d.mts} +1 -1
  412. package/dist/{placeholder-CVBv5z8k.d.mts.map → placeholder-BevVKfay.d.mts.map} +1 -1
  413. package/dist/plugin-types.d.mts +1 -1
  414. package/dist/plugin-utils.d.mts +9 -9
  415. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  416. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  417. package/dist/{preview-BfuRkVKW.mjs → preview-Dqv2hwXr.mjs} +2 -2
  418. package/dist/{preview-BfuRkVKW.mjs.map → preview-Dqv2hwXr.mjs.map} +1 -1
  419. package/dist/{public-url-BFVC2OTJ.mjs → public-url-D_zARuvZ.mjs} +1 -1
  420. package/dist/{public-url-BFVC2OTJ.mjs.map → public-url-D_zARuvZ.mjs.map} +1 -1
  421. package/dist/{query-CbUcI4Xk.mjs → query-Crm038Mc.mjs} +9 -9
  422. package/dist/{query-CbUcI4Xk.mjs.map → query-Crm038Mc.mjs.map} +1 -1
  423. package/dist/{rate-limit-C7hjdkS5.mjs → rate-limit-hRTBqmw1.mjs} +2 -2
  424. package/dist/{rate-limit-C7hjdkS5.mjs.map → rate-limit-hRTBqmw1.mjs.map} +1 -1
  425. package/dist/{redirect-B_q19j4v.mjs → redirect-C-OOkyku.mjs} +1 -1
  426. package/dist/{redirect-B_q19j4v.mjs.map → redirect-C-OOkyku.mjs.map} +1 -1
  427. package/dist/{redirects-CCbCqCCd.mjs → redirects-6Zg2SoYo.mjs} +8 -9
  428. package/dist/{redirects-CCbCqCCd.mjs.map → redirects-6Zg2SoYo.mjs.map} +1 -1
  429. package/dist/{redirects-DxVoR7PI.mjs → redirects-CP3TnTLO.mjs} +20 -14
  430. package/dist/redirects-CP3TnTLO.mjs.map +1 -0
  431. package/dist/{registry-brYh-rAT.mjs → registry-diMzD1Wf.mjs} +3 -3
  432. package/dist/{registry-brYh-rAT.mjs.map → registry-diMzD1Wf.mjs.map} +1 -1
  433. package/dist/{request-cache-D32LpnmI.mjs → request-cache-UwmBAiUK.mjs} +1 -1
  434. package/dist/{request-cache-D32LpnmI.mjs.map → request-cache-UwmBAiUK.mjs.map} +1 -1
  435. package/dist/{request-meta-7ByVLxB-.mjs → request-meta-DPechd0W.mjs} +2 -2
  436. package/dist/{request-meta-7ByVLxB-.mjs.map → request-meta-DPechd0W.mjs.map} +1 -1
  437. package/dist/{resolve-BqYMVG0D.mjs → resolve-B3NUUtVY.mjs} +1 -1
  438. package/dist/{resolve-BqYMVG0D.mjs.map → resolve-B3NUUtVY.mjs.map} +1 -1
  439. package/dist/{runner-DTdhuI9i.d.mts → runner-C8vcbvCe.d.mts} +2 -2
  440. package/dist/{runner-DTdhuI9i.d.mts.map → runner-C8vcbvCe.d.mts.map} +1 -1
  441. package/dist/runtime.d.mts +10 -10
  442. package/dist/runtime.mjs +1 -1
  443. package/dist/{schema-C1E70ug_.mjs → schema-BDOkd3OU.mjs} +4 -4
  444. package/dist/{schema-C1E70ug_.mjs.map → schema-BDOkd3OU.mjs.map} +1 -1
  445. package/dist/{search-B3SGZw91.mjs → search-Bs_J_EW-.mjs} +3 -3
  446. package/dist/{search-B3SGZw91.mjs.map → search-Bs_J_EW-.mjs.map} +1 -1
  447. package/dist/{secrets-ChPTmy9x.mjs → secrets-C8xmE6mR.mjs} +21 -11
  448. package/dist/secrets-C8xmE6mR.mjs.map +1 -0
  449. package/dist/{sections-D_lVzwRZ.mjs → sections-P0zuBlyz.mjs} +2 -2
  450. package/dist/{sections-D_lVzwRZ.mjs.map → sections-P0zuBlyz.mjs.map} +1 -1
  451. package/dist/seed/index.d.mts +2 -2
  452. package/dist/seed/index.mjs +14 -13
  453. package/dist/seo/index.d.mts +1 -1
  454. package/dist/seo/index.mjs +1 -1
  455. package/dist/{seo-D_LPkOtu.mjs → seo-CLhm-Fmb.mjs} +1 -1
  456. package/dist/{seo-D_LPkOtu.mjs.map → seo-CLhm-Fmb.mjs.map} +1 -1
  457. package/dist/{seo-B5e6y9Wk.mjs → seo-DpNgGQjF.mjs} +1 -1
  458. package/dist/{seo-B5e6y9Wk.mjs.map → seo-DpNgGQjF.mjs.map} +1 -1
  459. package/dist/{service-ChDcsTBs.mjs → service-CDQQnT8W.mjs} +2 -2
  460. package/dist/{service-ChDcsTBs.mjs.map → service-CDQQnT8W.mjs.map} +1 -1
  461. package/dist/{settings-DfxiWY_s.mjs → settings-BjBsmVAo.mjs} +10 -184
  462. package/dist/settings-BjBsmVAo.mjs.map +1 -0
  463. package/dist/{settings-Cv47v9u8.mjs → settings-sO0Fif4p.mjs} +2 -2
  464. package/dist/{settings-Cv47v9u8.mjs.map → settings-sO0Fif4p.mjs.map} +1 -1
  465. package/dist/{setup-complete-yvPE4OsP.mjs → setup-complete-CMMr-oZU.mjs} +1 -1
  466. package/dist/{setup-complete-yvPE4OsP.mjs.map → setup-complete-CMMr-oZU.mjs.map} +1 -1
  467. package/dist/{setup-nonce-C9aFzb94.mjs → setup-nonce-169xl4fV.mjs} +1 -1
  468. package/dist/{setup-nonce-C9aFzb94.mjs.map → setup-nonce-169xl4fV.mjs.map} +1 -1
  469. package/dist/single-flight-cache-C0UV1Npg.mjs +104 -0
  470. package/dist/single-flight-cache-C0UV1Npg.mjs.map +1 -0
  471. package/dist/{site-url-CnHlmAs9.mjs → site-url-vtsuOvSD.mjs} +1 -1
  472. package/dist/{site-url-CnHlmAs9.mjs.map → site-url-vtsuOvSD.mjs.map} +1 -1
  473. package/dist/{ssrf-BsVGIE0Z.mjs → ssrf-XO05Voq6.mjs} +1 -1
  474. package/dist/{ssrf-BsVGIE0Z.mjs.map → ssrf-XO05Voq6.mjs.map} +1 -1
  475. package/dist/status-2gZklYuj.mjs +30 -0
  476. package/dist/status-2gZklYuj.mjs.map +1 -0
  477. package/dist/storage/local.d.mts +1 -1
  478. package/dist/storage/local.mjs +2 -2
  479. package/dist/storage/s3.d.mts +1 -1
  480. package/dist/storage/s3.mjs +1 -1
  481. package/dist/{taxonomies-BdAmbOwx.mjs → taxonomies-BBxYA38v.mjs} +6 -6
  482. package/dist/{taxonomies-BdAmbOwx.mjs.map → taxonomies-BBxYA38v.mjs.map} +1 -1
  483. package/dist/{taxonomies-BILwiyGk.mjs → taxonomies-DuESHWKI.mjs} +2 -2
  484. package/dist/{taxonomies-BILwiyGk.mjs.map → taxonomies-DuESHWKI.mjs.map} +1 -1
  485. package/dist/{tokens-Bx2afeT-.mjs → tokens-DMkVjxrx.mjs} +1 -1
  486. package/dist/{tokens-Bx2afeT-.mjs.map → tokens-DMkVjxrx.mjs.map} +1 -1
  487. package/dist/{transport-CmpLD7W3.mjs → transport-1cIrOb1Y.mjs} +1 -1
  488. package/dist/{transport-CmpLD7W3.mjs.map → transport-1cIrOb1Y.mjs.map} +1 -1
  489. package/dist/{transport-B7PPP2CC.d.mts → transport-jdvsZEIt.d.mts} +1 -1
  490. package/dist/{transport-B7PPP2CC.d.mts.map → transport-jdvsZEIt.d.mts.map} +1 -1
  491. package/dist/{trusted-proxy-B4AfnoAp.mjs → trusted-proxy-CHp41Fjj.mjs} +1 -1
  492. package/dist/{trusted-proxy-B4AfnoAp.mjs.map → trusted-proxy-CHp41Fjj.mjs.map} +1 -1
  493. package/dist/{types-BFgrqwSk.d.mts → types-BFgYtuKd.d.mts} +1 -1
  494. package/dist/{types-BFgrqwSk.d.mts.map → types-BFgYtuKd.d.mts.map} +1 -1
  495. package/dist/{types-DZk_y-MU.mjs → types-BIduXPJk.mjs} +1 -1
  496. package/dist/{types-DZk_y-MU.mjs.map → types-BIduXPJk.mjs.map} +1 -1
  497. package/dist/{types-DTniiNto.d.mts → types-BTnnBYVX.d.mts} +2 -2
  498. package/dist/{types-DTniiNto.d.mts.map → types-BTnnBYVX.d.mts.map} +1 -1
  499. package/dist/{types-BUUVn1zr.d.mts → types-Bzfk2yC8.d.mts} +1 -1
  500. package/dist/{types-BUUVn1zr.d.mts.map → types-Bzfk2yC8.d.mts.map} +1 -1
  501. package/dist/{types-BH8-30hc.d.mts → types-CkEuk-Zr.d.mts} +1 -1
  502. package/dist/{types-BH8-30hc.d.mts.map → types-CkEuk-Zr.d.mts.map} +1 -1
  503. package/dist/{types-CPAPl93j.d.mts → types-DO7whVYU.d.mts} +2 -2
  504. package/dist/{types-CPAPl93j.d.mts.map → types-DO7whVYU.d.mts.map} +1 -1
  505. package/dist/{types-S15DXXNi.d.mts → types-DdkL6fyv.d.mts} +1 -1
  506. package/dist/{types-S15DXXNi.d.mts.map → types-DdkL6fyv.d.mts.map} +1 -1
  507. package/dist/{types-DpFmlNyB.mjs → types-DejCHqWT.mjs} +1 -1
  508. package/dist/{types-DpFmlNyB.mjs.map → types-DejCHqWT.mjs.map} +1 -1
  509. package/dist/{types-BPzXTV9x.d.mts → types-Del0VMij.d.mts} +1 -1
  510. package/dist/{types-BPzXTV9x.d.mts.map → types-Del0VMij.d.mts.map} +1 -1
  511. package/dist/{types-D4kUqbHh.d.mts → types-u_XxjbS8.d.mts} +1 -1
  512. package/dist/{types-D4kUqbHh.d.mts.map → types-u_XxjbS8.d.mts.map} +1 -1
  513. package/dist/{utils-C4Ih4DML.mjs → utils-C4M981Br.mjs} +1 -1
  514. package/dist/{utils-C4Ih4DML.mjs.map → utils-C4M981Br.mjs.map} +1 -1
  515. package/dist/{validate-Bz4vqcX1.mjs → validate-DGhQPXzI.mjs} +2 -2
  516. package/dist/{validate-Bz4vqcX1.mjs.map → validate-DGhQPXzI.mjs.map} +1 -1
  517. package/dist/{validate-CNwkPWzz.d.mts → validate-cJOiOvT2.d.mts} +5 -5
  518. package/dist/{validate-CNwkPWzz.d.mts.map → validate-cJOiOvT2.d.mts.map} +1 -1
  519. package/dist/{validation-DgGTJm3u.mjs → validation-DVHjPM1M.mjs} +5 -5
  520. package/dist/{validation-DgGTJm3u.mjs.map → validation-DVHjPM1M.mjs.map} +1 -1
  521. package/dist/version-BOjj_cfz.mjs +7 -0
  522. package/dist/{version-D-5txk2m.mjs.map → version-BOjj_cfz.mjs.map} +1 -1
  523. package/dist/{widgets-DZfmAbE4.mjs → widgets-Ci6hLwfO.mjs} +4 -4
  524. package/dist/{widgets-DZfmAbE4.mjs.map → widgets-Ci6hLwfO.mjs.map} +1 -1
  525. package/dist/{zod-generator-Djo_VHCt.mjs → zod-generator-CarzgPAu.mjs} +2 -2
  526. package/dist/{zod-generator-Djo_VHCt.mjs.map → zod-generator-CarzgPAu.mjs.map} +1 -1
  527. package/package.json +5 -5
  528. package/src/api/handlers/redirects.ts +24 -13
  529. package/src/api/schemas/redirects.ts +11 -4
  530. package/src/astro/integration/index.ts +44 -8
  531. package/src/astro/integration/routes.ts +46 -9
  532. package/src/astro/middleware/redirect.ts +12 -0
  533. package/src/bylines/field-defs-cache.ts +70 -20
  534. package/src/cli/commands/doctor.ts +1 -1
  535. package/src/config/secrets.ts +28 -14
  536. package/src/emdash-runtime.ts +5 -5
  537. package/src/redirects/status.ts +27 -0
  538. package/src/settings/index.ts +13 -13
  539. package/src/utils/{isolate-cache.ts → single-flight-cache.ts} +26 -21
  540. package/dist/byline-DUx48sJp.mjs.map +0 -1
  541. package/dist/redirects-DxVoR7PI.mjs.map +0 -1
  542. package/dist/secrets-ChPTmy9x.mjs.map +0 -1
  543. package/dist/settings-DfxiWY_s.mjs.map +0 -1
  544. package/dist/version-D-5txk2m.mjs +0 -7
  545. /package/dist/{api-tokens-Oq39ba-Z.mjs → api-tokens-C7ywRx7l.mjs} +0 -0
  546. /package/dist/{ssrf-BvgVcfNQ.mjs → ssrf-CRZGzjdL.mjs} +0 -0
  547. /package/dist/{types-CZI4E3qG.mjs → types-BoRm8-pp.mjs} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-lock-DlBHjf9-.mjs","names":[],"sources":["../src/after.ts","../src/utils/init-lock.ts"],"sourcesContent":["/**\n * Defer work past the HTTP response.\n *\n * Use for bookkeeping that doesn't need to complete before the client\n * gets bytes — writes that record state, maintenance queries, cache\n * refreshes. `after()` hands the promise to the host's lifetime\n * extender when one is available (Cloudflare's `waitUntil` under\n * workerd), or fires-and-forgets on Node (the process lives for the\n * next request anyway).\n *\n * Host binding is resolved lazily via a dynamic import of the\n * `virtual:emdash/wait-until` virtual module. Lazy — rather than a\n * static top-level import — so tools that walk the dist in a plain\n * Node loader (`astro check`, Vitest, etc.) don't trip over the\n * `virtual:` scheme: they'd only fail if they actually called\n * `after()`, which they don't during type-checking.\n */\n\nexport type WaitUntilFn = (promise: Promise<unknown>) => void;\n\n// Resolves to the host's waitUntil if the adapter provided one, or\n// null otherwise. Kicked off once at module load; subsequent `after()`\n// calls see the cached result without re-importing.\nconst waitUntilReady: Promise<WaitUntilFn | null> = (async () => {\n\ttry {\n\t\t// @ts-ignore - virtual module, generated by the Astro integration\n\t\tconst mod = (await import(\"virtual:emdash/wait-until\")) as {\n\t\t\twaitUntil?: WaitUntilFn;\n\t\t};\n\t\treturn mod.waitUntil ?? null;\n\t} catch {\n\t\t// No virtual module available (Node-side tooling, tests without the\n\t\t// integration in scope). Fire-and-forget is the safe fallback.\n\t\treturn null;\n\t}\n})();\n// Surface rejections without making the module-load fail.\nwaitUntilReady.catch(() => {});\n\n/**\n * Schedule `fn` to run without blocking the response.\n *\n * Errors are caught and logged — a deferred task should never surface\n * as an unhandled rejection because the response is long gone. Callers\n * that care about errors should handle them inside `fn`.\n */\nexport function after(fn: () => void | Promise<void>): void {\n\tconst promise = Promise.resolve()\n\t\t.then(fn)\n\t\t.catch((error) => {\n\t\t\tconsole.error(\"[emdash] deferred task failed:\", error);\n\t\t});\n\n\t// Defer the lifetime-extender handoff to the microtask that resolves\n\t// waitUntilReady. On workerd this is effectively instant (the virtual\n\t// module is already loaded in the bundle); on Node the promise\n\t// resolves to null, so this is just one extra microtask and no-op.\n\tvoid waitUntilReady.then((waitUntil) => {\n\t\tif (waitUntil) waitUntil(promise);\n\t\treturn null;\n\t});\n}\n","/**\n * Reclaimable initialization lock for isolate-lifetime singletons.\n *\n * Guards \"first request initializes, everyone else waits\" sections\n * (runtime creation, database init) against a workerd failure mode: if the\n * request that owns the initialization is cancelled mid-await (client\n * disconnect, context teardown), its continuation — including any `finally`\n * that would release the lock — never runs. A plain boolean or shared\n * promise then stays stuck forever and every subsequent request in the\n * isolate hangs until the platform kills it (observed as 524s at the\n * 100-second wall limit, with the isolate poisoned until eviction).\n *\n * This lock instead records *when* the owner started. Waiters poll — we\n * deliberately never await a promise created by another request, which\n * workerd flags — and if the owner has held the lock past `deadlineMs`,\n * the next waiter assumes the owner is dead, reclaims the lock, and runs\n * the initialization itself. Waiters also give up after `maxWaitMs` so a\n * request degrades to an error response rather than hanging.\n */\n\nexport interface InitLock {\n\t/** Epoch ms when the current owner claimed the lock, or null when free. */\n\townerStartedAt: number | null;\n\t/**\n\t * Monotonic claim counter identifying the current owner. Release is\n\t * gated on it: a slow owner that finishes after a waiter has reclaimed\n\t * the lock must not clear the reclaimer's claim — that would let yet\n\t * another caller claim the lock and start a third concurrent init.\n\t */\n\tgeneration: number;\n}\n\nexport function createInitLock(): InitLock {\n\treturn { ownerStartedAt: null, generation: 0 };\n}\n\nexport interface InitLockOptions {\n\t/**\n\t * Reclaim the lock if the owner has held it longer than this. Must be\n\t * comfortably above the slowest legitimate init (cold migrations on a\n\t * contended D1, including the concurrent-migrator wait) — a too-short\n\t * deadline risks two concurrent inits, a too-long one delays recovery\n\t * of a poisoned isolate. Nested locks must compose: an outer lock's\n\t * deadline must exceed the deadline of any lock its init acquires.\n\t */\n\tdeadlineMs?: number;\n\t/** Waiter poll interval. */\n\tpollMs?: number;\n\t/**\n\t * Give up waiting after this long and throw instead of hanging.\n\t * Defaults to `deadlineMs` plus headroom so a waiter always survives\n\t * long enough to reclaim a dead owner before giving up.\n\t */\n\tmaxWaitMs?: number;\n\t/**\n\t * Called with the in-flight init promise (errors pre-swallowed) so the\n\t * caller can hand it to the host's lifetime extender (waitUntil via\n\t * `after()`). If the owning request is cancelled mid-init, the anchored\n\t * promise keeps the context alive: init completes, populates the cache,\n\t * and the `finally` below releases the lock — preventing the poisoning\n\t * instead of merely recovering from it via reclaim.\n\t */\n\tanchor?: (promise: Promise<void>) => void;\n}\n\nconst DEFAULT_DEADLINE_MS = 15_000;\nconst DEFAULT_POLL_MS = 50;\nconst MAX_WAIT_HEADROOM_MS = 15_000;\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Return the cached value if present, otherwise initialize it under the\n * lock. `init` is responsible for storing the value so that `getCached`\n * returns it on subsequent calls — waiters re-check `getCached` after the\n * owner finishes rather than sharing the owner's promise.\n *\n * `init` receives an `isCurrentClaim` predicate and must gate its cache\n * publication on it: a slow init that was reclaimed past the deadline\n * must not overwrite the value published by the reclaimer (for the\n * runtime singleton that would orphan the reclaimer's active cron\n * scheduler). A losing init should also tear down any side resources it\n * started, since its result will never be published.\n */\nexport async function initWithLock<T>(\n\tlock: InitLock,\n\tgetCached: () => T | null | undefined,\n\tinit: (isCurrentClaim: () => boolean) => Promise<T>,\n\toptions?: InitLockOptions,\n): Promise<T> {\n\tconst deadlineMs = options?.deadlineMs ?? DEFAULT_DEADLINE_MS;\n\tconst pollMs = options?.pollMs ?? DEFAULT_POLL_MS;\n\tconst maxWaitMs = options?.maxWaitMs ?? deadlineMs + MAX_WAIT_HEADROOM_MS;\n\t// Date.now() is deliberate and only works because every loop iteration\n\t// awaits: in workerd the clock only advances across I/O, so a sync spin\n\t// would never observe the deadline. Don't \"optimize\" away the sleep.\n\tconst waitStart = Date.now();\n\n\tfor (;;) {\n\t\tconst cached = getCached();\n\t\tif (cached !== null && cached !== undefined) {\n\t\t\treturn cached;\n\t\t}\n\n\t\tconst ownerStartedAt = lock.ownerStartedAt;\n\t\tif (ownerStartedAt === null || Date.now() - ownerStartedAt > deadlineMs) {\n\t\t\t// Free, or the owner has been gone past the deadline — claim it.\n\t\t\t// Synchronous between awaits, so two waiters can't both claim.\n\t\t\tlock.generation += 1;\n\t\t\tconst claim = lock.generation;\n\t\t\tlock.ownerStartedAt = Date.now();\n\t\t\ttry {\n\t\t\t\t// Promise.resolve().then(...) so a synchronous throw from\n\t\t\t\t// init still becomes a rejection after the anchor attaches.\n\t\t\t\tconst isCurrentClaim = () => lock.generation === claim;\n\t\t\t\tconst initPromise = Promise.resolve().then(() => init(isCurrentClaim));\n\t\t\t\toptions?.anchor?.(\n\t\t\t\t\tinitPromise.then(\n\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t\t() => undefined,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn await initPromise;\n\t\t\t} finally {\n\t\t\t\t// If this request dies mid-init unanchored this never runs;\n\t\t\t\t// the next waiter reclaims after deadlineMs instead. Release\n\t\t\t\t// only while still the current owner: a reclaimer may have\n\t\t\t\t// taken the lock while this (slow) init was running, and\n\t\t\t\t// clearing its claim would admit a third concurrent init.\n\t\t\t\tif (lock.generation === claim) {\n\t\t\t\t\tlock.ownerStartedAt = null;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Date.now() - waitStart > maxWaitMs) {\n\t\t\tthrow new Error(`initWithLock: timed out after ${maxWaitMs}ms waiting for initialization`);\n\t\t}\n\t\tawait sleep(pollMs);\n\t}\n}\n"],"mappings":";AAuBA,MAAM,kBAA+C,YAAY;AAChE,KAAI;AAKH,UAHa,MAAM,OAAO,8BAGf,aAAa;SACjB;AAGP,SAAO;;IAEL;AAEJ,eAAe,YAAY,GAAG;;;;;;;;AAS9B,SAAgB,MAAM,IAAsC;CAC3D,MAAM,UAAU,QAAQ,SAAS,CAC/B,KAAK,GAAG,CACR,OAAO,UAAU;AACjB,UAAQ,MAAM,kCAAkC,MAAM;GACrD;AAMH,CAAK,eAAe,MAAM,cAAc;AACvC,MAAI,UAAW,WAAU,QAAQ;AACjC,SAAO;GACN;;;;;AC5BH,SAAgB,iBAA2B;AAC1C,QAAO;EAAE,gBAAgB;EAAM,YAAY;EAAG;;AAgC/C,MAAM,sBAAsB;AAC5B,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;AAE7B,SAAS,MAAM,IAA2B;AACzC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;AAgBzD,eAAsB,aACrB,MACA,WACA,MACA,SACa;CACb,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,SAAS,SAAS,UAAU;CAClC,MAAM,YAAY,SAAS,aAAa,aAAa;CAIrD,MAAM,YAAY,KAAK,KAAK;AAE5B,UAAS;EACR,MAAM,SAAS,WAAW;AAC1B,MAAI,WAAW,QAAQ,WAAW,OACjC,QAAO;EAGR,MAAM,iBAAiB,KAAK;AAC5B,MAAI,mBAAmB,QAAQ,KAAK,KAAK,GAAG,iBAAiB,YAAY;AAGxE,QAAK,cAAc;GACnB,MAAM,QAAQ,KAAK;AACnB,QAAK,iBAAiB,KAAK,KAAK;AAChC,OAAI;IAGH,MAAM,uBAAuB,KAAK,eAAe;IACjD,MAAM,cAAc,QAAQ,SAAS,CAAC,WAAW,KAAK,eAAe,CAAC;AACtE,aAAS,SACR,YAAY,WACL,cACA,OACN,CACD;AACD,WAAO,MAAM;aACJ;AAMT,QAAI,KAAK,eAAe,MACvB,MAAK,iBAAiB;;;AAKzB,MAAI,KAAK,KAAK,GAAG,YAAY,UAC5B,OAAM,IAAI,MAAM,iCAAiC,UAAU,+BAA+B;AAE3F,QAAM,MAAM,OAAO"}
@@ -25,4 +25,4 @@ async function loadUserSeed() {
25
25
 
26
26
  //#endregion
27
27
  export { loadUserSeed as n, load_exports as r, loadSeed as t };
28
- //# sourceMappingURL=load-BBetCvLC.mjs.map
28
+ //# sourceMappingURL=load-Dq91b_DK.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-BBetCvLC.mjs","names":[],"sources":["../src/seed/load.ts"],"sourcesContent":["/**\n * Seed file loading\n *\n * Imports seed data from the virtual module, which embeds the user's seed file\n * (or the default seed) at Vite build time. This avoids runtime filesystem access,\n * which doesn't work in workerd/miniflare where process.cwd() returns \"/\".\n */\n\nimport type { SeedFile } from \"./types.js\";\n\ninterface SeedModule {\n\tseed: SeedFile;\n\tuserSeed: SeedFile | null;\n}\n\nasync function getSeedModule(): Promise<SeedModule> {\n\t// @ts-ignore - virtual module, only available within Vite runtime\n\treturn import(\"virtual:emdash/seed\") as Promise<SeedModule>;\n}\n\n/**\n * Load the seed file (user seed or default).\n */\nexport async function loadSeed(): Promise<SeedFile> {\n\tconst { seed } = await getSeedModule();\n\treturn seed;\n}\n\n/**\n * Load the user's seed file, or null if none exists.\n */\nexport async function loadUserSeed(): Promise<SeedFile | null> {\n\tconst { userSeed } = await getSeedModule();\n\treturn userSeed ?? null;\n}\n"],"mappings":";;;;;;;AAeA,eAAe,gBAAqC;AAEnD,QAAO,OAAO;;;;;AAMf,eAAsB,WAA8B;CACnD,MAAM,EAAE,SAAS,MAAM,eAAe;AACtC,QAAO;;;;;AAMR,eAAsB,eAAyC;CAC9D,MAAM,EAAE,aAAa,MAAM,eAAe;AAC1C,QAAO,YAAY"}
1
+ {"version":3,"file":"load-Dq91b_DK.mjs","names":[],"sources":["../src/seed/load.ts"],"sourcesContent":["/**\n * Seed file loading\n *\n * Imports seed data from the virtual module, which embeds the user's seed file\n * (or the default seed) at Vite build time. This avoids runtime filesystem access,\n * which doesn't work in workerd/miniflare where process.cwd() returns \"/\".\n */\n\nimport type { SeedFile } from \"./types.js\";\n\ninterface SeedModule {\n\tseed: SeedFile;\n\tuserSeed: SeedFile | null;\n}\n\nasync function getSeedModule(): Promise<SeedModule> {\n\t// @ts-ignore - virtual module, only available within Vite runtime\n\treturn import(\"virtual:emdash/seed\") as Promise<SeedModule>;\n}\n\n/**\n * Load the seed file (user seed or default).\n */\nexport async function loadSeed(): Promise<SeedFile> {\n\tconst { seed } = await getSeedModule();\n\treturn seed;\n}\n\n/**\n * Load the user's seed file, or null if none exists.\n */\nexport async function loadUserSeed(): Promise<SeedFile | null> {\n\tconst { userSeed } = await getSeedModule();\n\treturn userSeed ?? null;\n}\n"],"mappings":";;;;;;;AAeA,eAAe,gBAAqC;AAEnD,QAAO,OAAO;;;;;AAMf,eAAsB,WAA8B;CACnD,MAAM,EAAE,SAAS,MAAM,eAAe;AACtC,QAAO;;;;;AAMR,eAAsB,eAAyC;CAC9D,MAAM,EAAE,aAAa,MAAM,eAAe;AAC1C,QAAO,YAAY"}
@@ -3,7 +3,7 @@ import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
3
3
  import { a as isPostgres, i as currentTimestampValue } from "./dialect-helpers-DRI5pyY3.mjs";
4
4
  import { a as encodeCursor, i as decodeCursor } from "./types-BXSUSAjt.mjs";
5
5
  import { getRequestContext } from "./request-context.mjs";
6
- import { n as isMissingTableError, t as isMissingColumnError } from "./db-errors-CtzxKBxe.mjs";
6
+ import { n as isMissingTableError, t as isMissingColumnError } from "./db-errors-BluWkwGI.mjs";
7
7
  import { kyselyLogOption } from "./database/instrumentation.mjs";
8
8
  import { Kysely, sql } from "kysely";
9
9
 
@@ -594,4 +594,4 @@ function emdashLoader() {
594
594
 
595
595
  //#endregion
596
596
  export { loader_exports as i, emdashLoader as n, getDb as r, CURSOR_RAW_VALUES as t };
597
- //# sourceMappingURL=loader-ZN1ll-d-.mjs.map
597
+ //# sourceMappingURL=loader-BqWjcH3h.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader-ZN1ll-d-.mjs","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Astro Live Collections loader for EmDash\n *\n * This loader implements the Astro LiveLoader interface to fetch content\n * at runtime from the database, enabling live editing without rebuilds.\n *\n * Architecture:\n * - Single `_emdash` Astro collection handles all content types\n * - Dialect comes from virtual module (configured in astro.config.mjs)\n * - Each content type maps to its own database table: ec_posts, ec_products, etc.\n * - `getEmDashCollection()` / `getEmDashEntry()` wrap Astro's live collection API\n */\n\nimport type { LiveLoader } from \"astro/loaders\";\nimport { Kysely, sql, type Dialect } from \"kysely\";\n\nimport { currentTimestampValue, isPostgres } from \"./database/dialect-helpers.js\";\nimport { kyselyLogOption } from \"./database/instrumentation.js\";\nimport { decodeCursor, encodeCursor } from \"./database/repositories/types.js\";\nimport { validateIdentifier } from \"./database/validate.js\";\nimport type { Database } from \"./index.js\";\nimport { getRequestContext } from \"./request-context.js\";\nimport { isMissingColumnError, isMissingTableError } from \"./utils/db-errors.js\";\n\nconst FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * SEO columns joined in from `_emdash_seo` on the single-entry path, mapped to\n * aliased result keys. SEO lives in a side table, so a LEFT JOIN folds it into\n * the entry load at zero extra query cost; the result is surfaced as a nested\n * `data.seo` object (see extractSeo) rather than flat fields.\n *\n * The `_emdash_` prefix on the aliases guarantees they can never collide with\n * a content field. Field slugs must match `/^[a-z][a-z0-9_]*$/`, so a user can\n * legitimately define a `seo_title` field; selecting the joined column under\n * its bare name would shadow that field in the result set and drop the user's\n * value. The prefix (illegal as a leading slug char) sidesteps this entirely.\n */\nconst SEO_COLUMN_ALIASES: Record<string, string> = {\n\tseo_title: \"_emdash_seo_title\",\n\tseo_description: \"_emdash_seo_description\",\n\tseo_image: \"_emdash_seo_image\",\n\tseo_canonical: \"_emdash_seo_canonical\",\n\tseo_no_index: \"_emdash_seo_no_index\",\n};\n\n/** Aliased SEO result keys — excluded from generic field mapping. */\nconst SEO_ALIAS_COLUMNS = Object.values(SEO_COLUMN_ALIASES);\n\n/**\n * System columns excluded from entry.data\n * Note: slug is intentionally NOT excluded - it's useful as data.slug in templates\n */\nconst SYSTEM_COLUMNS = new Set([\n\t\"id\",\n\t// \"slug\" - kept in data for template access\n\t\"status\",\n\t\"author_id\",\n\t\"primary_byline_id\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t\"locale\",\n\t\"translation_group\",\n\t// Aliased SEO columns joined from _emdash_seo on the single-entry path.\n\t// Surfaced as a nested data.seo object (see extractSeo), never as flat\n\t// fields. The aliases are _emdash_-prefixed so they can't shadow a user\n\t// field named e.g. `seo_title`.\n\t...SEO_ALIAS_COLUMNS,\n]);\n\n/** Resolved SEO shape attached to `entry.data.seo`. Mirrors `ContentSeo`. */\ninterface EntrySeo {\n\ttitle: string | null;\n\tdescription: string | null;\n\timage: string | null;\n\tcanonical: string | null;\n\tnoIndex: boolean;\n}\n\n/**\n * Build a `data.seo` object from the joined `_emdash_seo` columns on a row.\n *\n * Returns `null` when no SEO row exists (LEFT JOIN miss → `seo_no_index` is\n * NULL, since the column is `NOT NULL DEFAULT 0` whenever a row is present).\n * Returning null keeps the `seo` key off entries that have none, so\n * `getSeoMeta()` falls back to its defaults exactly as before.\n */\nfunction extractSeo(row: Record<string, unknown>): EntrySeo | null {\n\tconst noIndex = row[SEO_COLUMN_ALIASES.seo_no_index];\n\tif (noIndex === null || noIndex === undefined) return null;\n\tconst title = row[SEO_COLUMN_ALIASES.seo_title];\n\tconst description = row[SEO_COLUMN_ALIASES.seo_description];\n\tconst image = row[SEO_COLUMN_ALIASES.seo_image];\n\tconst canonical = row[SEO_COLUMN_ALIASES.seo_canonical];\n\treturn {\n\t\ttitle: typeof title === \"string\" ? title : null,\n\t\tdescription: typeof description === \"string\" ? description : null,\n\t\timage: typeof image === \"string\" ? image : null,\n\t\tcanonical: typeof canonical === \"string\" ? canonical : null,\n\t\tnoIndex: noIndex === 1,\n\t};\n}\n\n/**\n * Get the table name for a collection type\n */\nfunction getTableName(type: string): string {\n\tvalidateIdentifier(type, \"collection type\");\n\treturn `ec_${type}`;\n}\n\n/**\n * Cache for taxonomy names (only used for the primary database).\n * Skipped when a per-request DB override is active (e.g. preview mode)\n * because the override DB may have different taxonomies.\n */\nlet taxonomyNames: Set<string> | null = null;\n\n/**\n * Get all taxonomy names (cached for the primary DB, bypassed only when\n * the per-request DB is an isolated instance — playground / DO preview).\n * Plain D1 Sessions routing shares schema with the singleton, so the\n * module-scoped cache stays valid.\n */\nasync function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {\n\tconst hasIsolatedDb = getRequestContext()?.dbIsIsolated === true;\n\n\tif (!hasIsolatedDb && taxonomyNames) {\n\t\treturn taxonomyNames;\n\t}\n\n\ttry {\n\t\tconst defs = await db.selectFrom(\"_emdash_taxonomy_defs\").select(\"name\").execute();\n\t\tconst names = new Set(defs.map((d) => d.name));\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = names;\n\t\t}\n\t\treturn names;\n\t} catch {\n\t\t// Table doesn't exist yet, return empty set\n\t\tconst empty = new Set<string>();\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = empty;\n\t\t}\n\t\treturn empty;\n\t}\n}\n\n/**\n * System columns to include in data (mapped to camelCase where needed)\n */\nconst INCLUDE_IN_DATA: Record<string, string> = {\n\tid: \"id\",\n\tstatus: \"status\",\n\tauthor_id: \"authorId\",\n\tprimary_byline_id: \"primaryBylineId\",\n\tcreated_at: \"createdAt\",\n\tupdated_at: \"updatedAt\",\n\tpublished_at: \"publishedAt\",\n\tscheduled_at: \"scheduledAt\",\n\tdraft_revision_id: \"draftRevisionId\",\n\tlive_revision_id: \"liveRevisionId\",\n\tlocale: \"locale\",\n\ttranslation_group: \"translationGroup\",\n};\n\n/** System date columns that should be converted to Date objects */\nconst DATE_COLUMNS = new Set([\"created_at\", \"updated_at\", \"published_at\", \"scheduled_at\"]);\n\n/**\n * Hidden, symbol-keyed property on each mapped data record carrying the raw\n * DB string for every date column. Lets cursor encoders downstream reproduce\n * the loader's exact `nextCursor` format without round-tripping through\n * `new Date()`, which loses precision for stored values that aren't already\n * ISO-with-milliseconds (e.g. `2026-01-01T00:00:00Z` becomes\n * `2026-01-01T00:00:00.000Z`).\n */\nexport const CURSOR_RAW_VALUES: unique symbol = Symbol(\"emdash:cursorRawValues\");\n\nconst LOCAL_MEDIA_FILE_PREFIX = \"/_emdash/api/media/file/\";\nconst URL_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/;\n\n/** Safely extract a string value from a record, returning fallback if not a string */\nfunction rowStr(row: Record<string, unknown>, key: string, fallback = \"\"): string {\n\tconst val = row[key];\n\treturn typeof val === \"string\" ? val : fallback;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBareMediaKey(src: string): boolean {\n\treturn !src.startsWith(\"/\") && !URL_SCHEME_PATTERN.test(src);\n}\n\nfunction normalizeLocalMediaValue(value: unknown): unknown {\n\tif (Array.isArray(value)) {\n\t\treturn value.map(normalizeLocalMediaValue);\n\t}\n\n\tif (!isRecord(value)) {\n\t\treturn value;\n\t}\n\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, child] of Object.entries(value)) {\n\t\tnormalized[key] = normalizeLocalMediaValue(child);\n\t}\n\n\tif (\n\t\tnormalized.provider === \"local\" &&\n\t\ttypeof normalized.src === \"string\" &&\n\t\tnormalized.src.length > 0\n\t) {\n\t\tconst src = normalized.src;\n\t\tif (src.startsWith(LOCAL_MEDIA_FILE_PREFIX)) {\n\t\t\tconst id = src.slice(LOCAL_MEDIA_FILE_PREFIX.length);\n\t\t\tif (!normalized.id && id) {\n\t\t\t\tnormalized.id = id;\n\t\t\t}\n\t\t} else if (isBareMediaKey(src)) {\n\t\t\tif (!normalized.id) {\n\t\t\t\tnormalized.id = src;\n\t\t\t}\n\t\t\tnormalized.src = `${LOCAL_MEDIA_FILE_PREFIX}${src}`;\n\t\t}\n\t}\n\n\treturn normalized;\n}\n\n/**\n * Map a database row to entry data\n * Extracts content fields (non-system columns) and parses JSON where needed.\n * System columns needed for templates (id, status, dates) are included with camelCase names.\n */\nfunction mapRowToData(row: Record<string, unknown>): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {};\n\tconst rawDateValues: Record<string, string> = {};\n\n\tfor (const [key, value] of Object.entries(row)) {\n\t\t// Include certain system columns (mapped to camelCase where needed)\n\t\tif (key in INCLUDE_IN_DATA) {\n\t\t\t// Convert date columns from ISO strings to Date objects\n\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\trawDateValues[key] = value;\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = new Date(value);\n\t\t\t\t} else {\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = null;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata[INCLUDE_IN_DATA[key]] = value;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (SYSTEM_COLUMNS.has(key)) continue;\n\n\t\t// Try to parse JSON strings (for portableText, json fields, etc.)\n\t\tif (typeof value === \"string\") {\n\t\t\ttry {\n\t\t\t\t// Only parse if it looks like JSON (starts with { or [)\n\t\t\t\tif (value.startsWith(\"{\") || value.startsWith(\"[\")) {\n\t\t\t\t\tdata[key] = normalizeLocalMediaValue(JSON.parse(value));\n\t\t\t\t} else {\n\t\t\t\t\tdata[key] = value;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tdata[key] = value;\n\t\t\t}\n\t\t} else {\n\t\t\tdata[key] = value;\n\t\t}\n\t}\n\n\tObject.defineProperty(data, CURSOR_RAW_VALUES, {\n\t\tvalue: rawDateValues,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t});\n\n\treturn data;\n}\n\n/**\n * Map revision data (already-parsed JSON object) to entry data.\n * Strips _-prefixed metadata keys (e.g. _slug) used internally by revisions.\n */\nfunction mapRevisionData(data: Record<string, unknown>): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (key.startsWith(\"_\")) continue; // revision metadata\n\t\tresult[key] = normalizeLocalMediaValue(value);\n\t}\n\treturn result;\n}\n\n// Virtual module imports are lazy-loaded to avoid errors when importing\n// emdash outside of Astro/Vite context (e.g., in astro.config.mjs)\nlet virtualConfig:\n\t| {\n\t\t\tdatabase?: { config: unknown };\n\t\t\ti18n?: { defaultLocale: string; locales: string[]; prefixDefaultLocale?: boolean } | null;\n\t }\n\t| undefined;\nlet virtualCreateDialect: ((config: unknown) => Dialect) | undefined;\n\nasync function loadVirtualModules() {\n\tif (virtualConfig === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst configModule = await import(\"virtual:emdash/config\");\n\t\tvirtualConfig = configModule.default;\n\t}\n\tif (virtualCreateDialect === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst dialectModule = await import(\"virtual:emdash/dialect\");\n\t\tvirtualCreateDialect = dialectModule.createDialect;\n\t\t// dialectType is no longer needed here — dialect detection is\n\t\t// done via the db adapter instance in dialect-helpers.ts\n\t}\n}\n\n/**\n * Entry data type - generic object\n */\nexport type EntryData = Record<string, unknown>;\n\n/**\n * Sort direction\n */\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Order by specification - field name to direction\n * @example { created_at: \"desc\" } - Sort by created_at descending\n * @example { title: \"asc\" } - Sort by title ascending\n */\nexport type OrderBySpec = Record<string, SortDirection>;\n\n/**\n * Build WHERE clause for status filtering.\n * When filtering for 'published' status, also include scheduled content\n * whose scheduled_at time has passed (treating it as effectively published).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nfunction buildStatusCondition(\n\tdb: Kysely<any>,\n\tstatus: string,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst statusField = tablePrefix ? `${tablePrefix}.status` : \"status\";\n\tconst scheduledAtField = tablePrefix ? `${tablePrefix}.scheduled_at` : \"scheduled_at\";\n\n\tif (status === \"published\") {\n\t\t// Include both published content AND scheduled content past its publish time.\n\t\t// scheduled_at is stored as text (ISO 8601). On Postgres, we must cast it\n\t\t// to timestamptz for the comparison with CURRENT_TIMESTAMP to work.\n\t\tconst scheduledAtExpr = isPostgres(db)\n\t\t\t? sql`${sql.ref(scheduledAtField)}::timestamptz`\n\t\t\t: sql.ref(scheduledAtField);\n\t\tconst nowExpr = isPostgres(db)\n\t\t\t? currentTimestampValue(db)\n\t\t\t: sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n\t\treturn sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${nowExpr}))`;\n\t}\n\n\treturn sql`${sql.ref(statusField)} = ${status}`;\n}\n\n/**\n * Resolved primary sort field and direction (used for cursor pagination).\n */\ninterface PrimarySort {\n\tfield: string;\n\tdirection: SortDirection;\n}\n\n/**\n * Get the primary sort field from an orderBy spec (first valid field, or default).\n */\nfunction getPrimarySort(orderBy: OrderBySpec | undefined, tablePrefix?: string): PrimarySort {\n\tif (orderBy) {\n\t\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t\tif (FIELD_NAME_PATTERN.test(field)) {\n\t\t\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\t\t\treturn { field: fullField, direction };\n\t\t\t}\n\t\t}\n\t}\n\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\treturn { field: defaultField, direction: \"desc\" };\n}\n\n/**\n * Build ORDER BY clause from orderBy spec\n * Validates field names to prevent SQL injection (alphanumeric + underscore only)\n * Supports multiple sort fields in object key order\n */\nfunction buildOrderByClause(\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\t// Default to created_at DESC\n\tif (!orderBy || Object.keys(orderBy).length === 0) {\n\t\tconst field = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(field)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\tconst sortParts: ReturnType<typeof sql>[] = [];\n\n\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t// Validate field name (alphanumeric + underscore only)\n\t\tif (!FIELD_NAME_PATTERN.test(field)) {\n\t\t\tcontinue; // Skip invalid field names\n\t\t}\n\n\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\tconst dir = direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\t\tsortParts.push(sql`${sql.ref(fullField)} ${dir}`);\n\t}\n\n\t// If no valid sort fields, fall back to default\n\tif (sortParts.length === 0) {\n\t\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(defaultField)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\t// Add id as tiebreaker to ensure stable cursor ordering\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\tconst idDir = primary.direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\tsortParts.push(sql`${sql.ref(idField)} ${idDir}`);\n\n\treturn sql`ORDER BY ${sql.join(sortParts, sql`, `)}`;\n}\n\n/**\n * Build a cursor WHERE condition for keyset pagination.\n * Uses the primary sort field + id as tiebreaker for stable ordering.\n *\n * Throws `InvalidCursorError` if the cursor is malformed; callers should\n * let this propagate so users see a real error rather than silently\n * falling back to the first page.\n */\nfunction buildCursorCondition(\n\tcursor: string,\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst { orderValue, id: cursorId } = decodeCursor(cursor);\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\n\tif (primary.direction === \"desc\") {\n\t\treturn sql`(${sql.ref(primary.field)} < ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} < ${cursorId}))`;\n\t}\n\treturn sql`(${sql.ref(primary.field)} > ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} > ${cursorId}))`;\n}\n\n/** Type guard: is the where value a range object (not a string or array)? */\nfunction isWhereRange(value: WhereValue): value is WhereRange {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Build AND conditions for non-taxonomy field filters.\n * Returns an array of sql fragments; empty if no field filters apply.\n * Field names are validated against FIELD_NAME_PATTERN to prevent injection.\n */\nfunction buildFieldConditions(\n\tfields: Record<string, WhereValue>,\n\ttablePrefix?: string,\n): ReturnType<typeof sql>[] {\n\tconst conditions: ReturnType<typeof sql>[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (!FIELD_NAME_PATTERN.test(key)) {\n\t\t\tconsole.warn(`[emdash] where filter: invalid field name \"${key}\" ignored`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (value == null) continue;\n\t\tconst ref = tablePrefix ? sql.ref(`${tablePrefix}.${key}`) : sql.ref(key);\n\n\t\tif (isWhereRange(value)) {\n\t\t\tif (value.gt !== undefined) conditions.push(sql`${ref} > ${value.gt}`);\n\t\t\tif (value.gte !== undefined) conditions.push(sql`${ref} >= ${value.gte}`);\n\t\t\tif (value.lt !== undefined) conditions.push(sql`${ref} < ${value.lt}`);\n\t\t\tif (value.lte !== undefined) conditions.push(sql`${ref} <= ${value.lte}`);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tif (value.length > 0) {\n\t\t\t\tconditions.push(sql`${ref} IN (${sql.join(value.map((v) => sql`${v}`))})`);\n\t\t\t}\n\t\t} else {\n\t\t\tconditions.push(sql`${ref} = ${value}`);\n\t\t}\n\t}\n\n\treturn conditions;\n}\n\n/**\n * Range filter for comparison operators on field values.\n * Values are compared as strings in the database. This works correctly for\n * ISO 8601 dates (e.g. \"2024-01-01T00:00:00Z\") because lexicographic ordering\n * matches chronological ordering. Ensure date values use a consistent format.\n */\nexport interface WhereRange {\n\tgt?: string;\n\tgte?: string;\n\tlt?: string;\n\tlte?: string;\n}\n\n/**\n * A where clause value: exact match, multi-value match, or range comparison.\n */\nexport type WhereValue = string | string[] | WhereRange;\n\n/**\n * Filter for loadCollection - type is required\n */\nexport interface CollectionFilter {\n\ttype: string;\n\tstatus?: \"draft\" | \"published\" | \"archived\";\n\tlimit?: number;\n\t/**\n\t * Opaque cursor for keyset pagination.\n\t * Pass the `nextCursor` value from a previous result to fetch the next page.\n\t */\n\tcursor?: string;\n\t/**\n\t * Filter by field values, taxonomy terms, byline credits, or ranges.\n\t *\n\t * Taxonomy names are detected automatically and filtered via JOIN.\n\t * The reserved `byline` key filters by byline credit (any credit, not\n\t * just the primary one) via the `_emdash_content_bylines` junction\n\t * table; its value is one or more byline translation groups.\n\t * Other keys are treated as column filters on the content table.\n\t *\n\t * @example { category: 'news' } - taxonomy term\n\t * @example { byline: '01HXYZ...' } - entries credited to a byline (any position)\n\t * @example { series: 'main' } - exact match on a content field\n\t * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - date range\n\t */\n\twhere?: Record<string, WhereValue>;\n\t/**\n\t * Order results by field(s)\n\t * @default { created_at: \"desc\" }\n\t */\n\torderBy?: OrderBySpec;\n\t/**\n\t * Filter by locale (e.g. 'en', 'fr').\n\t * When set, only returns content in this locale.\n\t */\n\tlocale?: string;\n}\n\n/**\n * Filter for loadEntry - type and id are required\n */\nexport interface EntryFilter {\n\ttype: string;\n\tid: string;\n\t/**\n\t * When set, fetch content data from this revision instead of the content table.\n\t * Used by preview mode to serve draft revision data.\n\t */\n\trevisionId?: string;\n\t/**\n\t * Locale to scope slug lookup. Only affects slug resolution;\n\t * IDs are globally unique and always resolve regardless of locale.\n\t */\n\tlocale?: string;\n}\n\n// Cached database instance (shared across calls)\nlet dbInstance: Kysely<Database> | null = null;\n\n/**\n * Get the database instance. Used by query wrapper functions and middleware.\n *\n * Checks the ALS request context first — if a per-request DB override is set\n * (e.g. by DO preview middleware), it takes precedence over the module-level\n * cached instance. This allows preview mode to route queries to an isolated\n * Durable Object database without modifying any calling code.\n *\n * Initializes the default database on first call using config from virtual module.\n */\nexport async function getDb(): Promise<Kysely<Database>> {\n\t// Per-request DB override via ALS (normal mode)\n\tconst ctx = getRequestContext();\n\tif (ctx?.db) {\n\t\treturn ctx.db as Kysely<Database>; // eslint-disable-line typescript/no-unsafe-type-assertion -- db is typed as unknown in RequestContext to avoid circular deps\n\t}\n\n\tif (!dbInstance) {\n\t\tawait loadVirtualModules();\n\t\tif (!virtualConfig?.database || typeof virtualCreateDialect !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"EmDash database not configured. Add database config to emdash() in astro.config.mjs\",\n\t\t\t);\n\t\t}\n\t\tconst dialect = virtualCreateDialect(virtualConfig.database.config);\n\t\tdbInstance = new Kysely<Database>({ dialect, log: kyselyLogOption() });\n\t}\n\treturn dbInstance;\n}\n\n/**\n * Create an EmDash Live Collections loader\n *\n * This loader handles ALL content types in a single Astro collection.\n * Use `getEmDashCollection()` and `getEmDashEntry()` to query\n * specific content types.\n *\n * Database is configured in astro.config.mjs via the emdash() integration.\n *\n * @example\n * ```ts\n * // src/live.config.ts\n * import { defineLiveCollection } from \"astro:content\";\n * import { emdashLoader } from \"emdash\";\n *\n * export const collections = {\n * emdash: defineLiveCollection({\n * loader: emdashLoader(),\n * }),\n * };\n * ```\n */\nexport function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFilter> {\n\treturn {\n\t\tname: \"emdash\",\n\n\t\t/**\n\t\t * Load all entries for a content type\n\t\t */\n\t\tasync loadCollection({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance (initializes on first use)\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Type filter is required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tif (!type) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type filter is required. Use getEmDashCollection() instead of getLiveCollection() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table (ec_posts, ec_products, etc.)\n\t\t\t\tconst tableName = getTableName(type);\n\n\t\t\t\t// Build query with dynamic table name\n\t\t\t\tconst status = filter?.status || \"published\";\n\t\t\t\tconst limit = filter?.limit;\n\t\t\t\tconst cursor = filter?.cursor;\n\t\t\t\tconst where = filter?.where;\n\t\t\t\tconst orderBy = filter?.orderBy;\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Cursor pagination: over-fetch by 1 to detect next page\n\t\t\t\tconst fetchLimit = limit ? limit + 1 : undefined;\n\n\t\t\t\t// Build cursor condition if cursor is provided\n\t\t\t\tconst cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;\n\n\t\t\t\t// Separate taxonomy / byline filters from field filters\n\t\t\t\tlet result: { rows: Record<string, unknown>[] };\n\t\t\t\t// Taxonomy filters AND together: each entry constrains the base\n\t\t\t\t// row to match at least one of its slugs *within that taxonomy*.\n\t\t\t\t// Term slugs are unique only within a taxonomy, so every filter\n\t\t\t\t// keeps its own `name` and emits its own `EXISTS` clause rather\n\t\t\t\t// than pooling slugs into one `IN`.\n\t\t\t\tconst taxonomyFilters: { name: string; slugs: string[] }[] = [];\n\t\t\t\t// A byline filter matches entries credited to any of the given\n\t\t\t\t// byline translation groups via the `_emdash_content_bylines`\n\t\t\t\t// junction table. `null` means no byline filter; an empty\n\t\t\t\t// `groups` array means the filter was requested but matches\n\t\t\t\t// nothing (short-circuited to an empty result below).\n\t\t\t\tlet bylineFilter: { groups: string[] } | null = null;\n\t\t\t\tconst fieldFilters: Record<string, WhereValue> = {};\n\n\t\t\t\tif (where && Object.keys(where).length > 0) {\n\t\t\t\t\tconst taxNames = await getTaxonomyNames(db);\n\n\t\t\t\t\tfor (const [key, value] of Object.entries(where)) {\n\t\t\t\t\t\tif (value == null) continue;\n\t\t\t\t\t\tif (key === \"byline\") {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on \"byline\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst groups = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\tbylineFilter = { groups };\n\t\t\t\t\t\t} else if (taxNames.has(key)) {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on taxonomy \"${key}\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst slugs = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\ttaxonomyFilters.push({ name: key, slugs });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfieldFilters[key] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A byline or taxonomy filter with no values matches nothing —\n\t\t\t\t// short-circuit before building SQL (an empty `IN ()` is invalid\n\t\t\t\t// SQL on both dialects).\n\t\t\t\tif (\n\t\t\t\t\t(bylineFilter && bylineFilter.groups.length === 0) ||\n\t\t\t\t\ttaxonomyFilters.some((f) => f.slugs.length === 0)\n\t\t\t\t) {\n\t\t\t\t\treturn { entries: [], cacheHint: { tags: [type] } };\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\t// Taxonomy and byline filters are applied as correlated\n\t\t\t\t\t// `EXISTS` semi-joins rather than `INNER JOIN ... DISTINCT`.\n\t\t\t\t\t// A join fan-out would force `SELECT DISTINCT table.*`, and\n\t\t\t\t\t// Postgres cannot apply DISTINCT to a row containing a `json`\n\t\t\t\t\t// column (no equality operator), so the join approach throws\n\t\t\t\t\t// there. EXISTS matches \"credited/tagged at least once\"\n\t\t\t\t\t// without duplicating rows, needs no DISTINCT, and works on\n\t\t\t\t\t// both SQLite and Postgres. The base query stays a single-\n\t\t\t\t\t// table `SELECT *`, so all field/status/locale/cursor/order\n\t\t\t\t\t// conditions reference unprefixed columns as before.\n\t\t\t\t\tconst orderByClause = buildOrderByClause(orderBy);\n\t\t\t\t\tconst statusCondition = buildStatusCondition(db, status);\n\t\t\t\t\tconst localeFilter = locale ? sql`AND locale = ${locale}` : sql``;\n\t\t\t\t\tconst cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;\n\t\t\t\t\tconst fieldConds = buildFieldConditions(fieldFilters);\n\t\t\t\t\tconst fieldCondsSQL =\n\t\t\t\t\t\tfieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;\n\n\t\t\t\t\t// One `EXISTS` per taxonomy, AND'd together: an entry must be\n\t\t\t\t\t// tagged with a matching term in *every* requested taxonomy.\n\t\t\t\t\t// Each clause pins its own `t.name`, so slugs never pool\n\t\t\t\t\t// across taxonomies (they're only unique within one).\n\t\t\t\t\tconst taxonomyCond =\n\t\t\t\t\t\ttaxonomyFilters.length > 0\n\t\t\t\t\t\t\t? sql`${sql.join(\n\t\t\t\t\t\t\t\t\ttaxonomyFilters.map(\n\t\t\t\t\t\t\t\t\t\t(f) => sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM content_taxonomies ct\n\t\t\t\t\t\t\tINNER JOIN taxonomies t ON t.id = ct.taxonomy_id\n\t\t\t\t\t\t\tWHERE ct.collection = ${type}\n\t\t\t\t\t\t\t\tAND ct.entry_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND t.name = ${f.name}\n\t\t\t\t\t\t\t\tAND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})\n\t\t\t\t\t\t)`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tsql` `,\n\t\t\t\t\t\t\t\t)}`\n\t\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\t// `_emdash_content_bylines.byline_id` stores the byline's\n\t\t\t\t\t// translation_group (migration 040), so a credit spans every\n\t\t\t\t\t// locale variant of the byline and we match the group directly.\n\t\t\t\t\tconst bylineCond = bylineFilter\n\t\t\t\t\t\t? sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM _emdash_content_bylines cb\n\t\t\t\t\t\t\tWHERE cb.collection_slug = ${type}\n\t\t\t\t\t\t\t\tAND cb.content_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND cb.byline_id IN (${sql.join(bylineFilter.groups.map((g) => sql`${g}`))})\n\t\t\t\t\t\t)`\n\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\tresult = await sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t\tAND ${statusCondition}\n\t\t\t\t\t\t${localeFilter}\n\t\t\t\t\t\t${cursorCond}\n\t\t\t\t\t\t${taxonomyCond}\n\t\t\t\t\t\t${bylineCond}\n\t\t\t\t\t\t${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}\n\t\t\t\t\t\t${orderByClause}\n\t\t\t\t\t\t${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}\n\t\t\t\t\t`.execute(db);\n\t\t\t\t}\n\n\t\t\t\t// Detect whether there are more results (over-fetched by 1)\n\t\t\t\tconst hasMore = limit ? result.rows.length > limit : false;\n\t\t\t\tconst rows = hasMore ? result.rows.slice(0, limit) : result.rows;\n\n\t\t\t\t// Map rows to entries\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entries = rows.map((row) => {\n\t\t\t\t\tconst slug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\t\tconst rowLocale = rowStr(row, \"locale\");\n\t\t\t\t\tconst shouldPrefix =\n\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\trowLocale !== \"\" &&\n\t\t\t\t\t\t(rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\tconst id = shouldPrefix ? `${rowLocale}/${slug}` : slug;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\tdata: mapRowToData(row),\n\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Encode nextCursor from the last row if there are more results\n\t\t\t\tlet nextCursor: string | undefined;\n\t\t\t\tif (hasMore && rows.length > 0) {\n\t\t\t\t\tconst lastRow = rows.at(-1)!;\n\t\t\t\t\tconst primary = getPrimarySort(orderBy);\n\t\t\t\t\t// Strip table prefix from field name for row lookup\n\t\t\t\t\tconst fieldName = primary.field.includes(\".\")\n\t\t\t\t\t\t? primary.field.split(\".\").pop()!\n\t\t\t\t\t\t: primary.field;\n\t\t\t\t\tconst lastOrderValue = lastRow[fieldName];\n\t\t\t\t\tconst orderStr =\n\t\t\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tnextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t\t\t}\n\n\t\t\t\t// Collection-level cache hint uses the most recent updated_at\n\t\t\t\tlet collectionLastModified: Date | undefined;\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tif (row.updated_at) {\n\t\t\t\t\t\tconst d = new Date(rowStr(row, \"updated_at\"));\n\t\t\t\t\t\tif (!collectionLastModified || d > collectionLastModified) {\n\t\t\t\t\t\t\tcollectionLastModified = d;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tentries,\n\t\t\t\t\tnextCursor,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [type],\n\t\t\t\t\t\tlastModified: collectionLastModified,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table/column gracefully - return empty collection.\n\t\t\t\t// Missing table happens before migrations have run.\n\t\t\t\t// Missing column happens when a where filter references a non-existent field.\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (isMissingTableError(error) || isMissingColumnError(error)) {\n\t\t\t\t\tif (isMissingColumnError(error)) {\n\t\t\t\t\t\tconsole.warn(`[emdash] where filter: ${message}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn { entries: [] };\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load collection: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load a single entry by type and ID/slug\n\t\t *\n\t\t * When filter.revisionId is set (preview mode), the entry's data\n\t\t * comes from the revisions table instead of the content table columns.\n\t\t */\n\t\tasync loadEntry({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Both type and id are required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tconst id = filter?.id;\n\n\t\t\t\tif (!type || !id) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type and id filters are required. Use getEmDashEntry() instead of getLiveEntry() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table\n\t\t\t\tconst tableName = getTableName(type);\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Use raw SQL for dynamic table name, match by slug or id\n\t\t\t\t// When locale is specified, prefer locale-scoped slug match,\n\t\t\t\t// but IDs are globally unique so always check id without locale scope.\n\t\t\t\t//\n\t\t\t\t// LEFT JOIN _emdash_seo folds per-entry SEO (canonical, noindex,\n\t\t\t\t// etc.) into this single query at zero extra round-trip cost. The\n\t\t\t\t// joined columns are surfaced as a nested data.seo object via\n\t\t\t\t// extractSeo() and excluded from the generic field mapping. SEO is\n\t\t\t\t// 1:1 with content (PK on collection+content_id), so the join never\n\t\t\t\t// multiplies rows.\n\t\t\t\tconst seoSelect = sql.join(\n\t\t\t\t\tObject.entries(SEO_COLUMN_ALIASES).map(\n\t\t\t\t\t\t([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst result = locale\n\t\t\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tLEFT JOIN ${sql.ref(\"_emdash_seo\")} AS s\n\t\t\t\t\t\t\t\tON s.collection = ${type} AND s.content_id = c.id\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND ((c.slug = ${id} AND c.locale = ${locale}) OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db)\n\t\t\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tLEFT JOIN ${sql.ref(\"_emdash_seo\")} AS s\n\t\t\t\t\t\t\t\tON s.collection = ${type} AND s.content_id = c.id\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND (c.slug = ${id} OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db);\n\n\t\t\t\tconst row = result.rows[0];\n\t\t\t\tif (!row) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entrySlug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\tconst entryLocale = rowStr(row, \"locale\");\n\t\t\t\tconst shouldPrefixEntry =\n\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\tentryLocale !== \"\" &&\n\t\t\t\t\t(entryLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\tconst entryId = shouldPrefixEntry ? `${entryLocale}/${entrySlug}` : entrySlug;\n\n\t\t\t\t// Preview mode: override content fields with revision data,\n\t\t\t\t// keeping system metadata from the content table row.\n\t\t\t\tconst revisionId = filter?.revisionId;\n\t\t\t\tif (revisionId) {\n\t\t\t\t\tconst revRow = await sql<{ data: string }>`\n\t\t\t\t\t\tSELECT data FROM revisions\n\t\t\t\t\t\tWHERE id = ${revisionId}\n\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t`.execute(db);\n\n\t\t\t\t\tconst revData = revRow.rows[0];\n\t\t\t\t\tif (revData) {\n\t\t\t\t\t\tconst parsed: Record<string, unknown> = JSON.parse(revData.data);\n\t\t\t\t\t\t// System metadata from content table + content fields from revision\n\t\t\t\t\t\tconst systemData: Record<string, unknown> = {};\n\t\t\t\t\t\tfor (const [key, mappedKey] of Object.entries(INCLUDE_IN_DATA)) {\n\t\t\t\t\t\t\tif (key in row) {\n\t\t\t\t\t\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = typeof row[key] === \"string\" ? new Date(row[key]) : null;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = row[key];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Use slug from revision metadata if present, else from content table\n\t\t\t\t\t\tconst slug = typeof parsed._slug === \"string\" ? parsed._slug : rowStr(row, \"slug\");\n\t\t\t\t\t\tconst revSlug = slug || rowStr(row, \"id\");\n\t\t\t\t\t\tconst revLocale = rowStr(row, \"locale\");\n\t\t\t\t\t\tconst shouldPrefixRev =\n\t\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\t\trevLocale !== \"\" &&\n\t\t\t\t\t\t\t(revLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\t\tconst revId = shouldPrefixRev ? `${revLocale}/${revSlug}` : revSlug;\n\t\t\t\t\t\t// SEO is not revisioned — it comes from the content row's\n\t\t\t\t\t\t// joined _emdash_seo columns, not the revision snapshot.\n\t\t\t\t\t\tconst revEntryData: Record<string, unknown> = {\n\t\t\t\t\t\t\t...systemData,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\t...mapRevisionData(parsed),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst revSeo = extractSeo(row);\n\t\t\t\t\t\tif (revSeo) revEntryData.seo = revSeo;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: revId,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\t\tdata: revEntryData,\n\t\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst entryData = mapRowToData(row);\n\t\t\t\tconst entrySeo = extractSeo(row);\n\t\t\t\tif (entrySeo) entryData.seo = entrySeo;\n\t\t\t\treturn {\n\t\t\t\t\tid: entryId,\n\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\tdata: entryData,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table gracefully - return undefined (not found).\n\t\t\t\t// This happens before migrations have run.\n\t\t\t\tif (isMissingTableError(error)) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load entry: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"loader-BqWjcH3h.mjs","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Astro Live Collections loader for EmDash\n *\n * This loader implements the Astro LiveLoader interface to fetch content\n * at runtime from the database, enabling live editing without rebuilds.\n *\n * Architecture:\n * - Single `_emdash` Astro collection handles all content types\n * - Dialect comes from virtual module (configured in astro.config.mjs)\n * - Each content type maps to its own database table: ec_posts, ec_products, etc.\n * - `getEmDashCollection()` / `getEmDashEntry()` wrap Astro's live collection API\n */\n\nimport type { LiveLoader } from \"astro/loaders\";\nimport { Kysely, sql, type Dialect } from \"kysely\";\n\nimport { currentTimestampValue, isPostgres } from \"./database/dialect-helpers.js\";\nimport { kyselyLogOption } from \"./database/instrumentation.js\";\nimport { decodeCursor, encodeCursor } from \"./database/repositories/types.js\";\nimport { validateIdentifier } from \"./database/validate.js\";\nimport type { Database } from \"./index.js\";\nimport { getRequestContext } from \"./request-context.js\";\nimport { isMissingColumnError, isMissingTableError } from \"./utils/db-errors.js\";\n\nconst FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * SEO columns joined in from `_emdash_seo` on the single-entry path, mapped to\n * aliased result keys. SEO lives in a side table, so a LEFT JOIN folds it into\n * the entry load at zero extra query cost; the result is surfaced as a nested\n * `data.seo` object (see extractSeo) rather than flat fields.\n *\n * The `_emdash_` prefix on the aliases guarantees they can never collide with\n * a content field. Field slugs must match `/^[a-z][a-z0-9_]*$/`, so a user can\n * legitimately define a `seo_title` field; selecting the joined column under\n * its bare name would shadow that field in the result set and drop the user's\n * value. The prefix (illegal as a leading slug char) sidesteps this entirely.\n */\nconst SEO_COLUMN_ALIASES: Record<string, string> = {\n\tseo_title: \"_emdash_seo_title\",\n\tseo_description: \"_emdash_seo_description\",\n\tseo_image: \"_emdash_seo_image\",\n\tseo_canonical: \"_emdash_seo_canonical\",\n\tseo_no_index: \"_emdash_seo_no_index\",\n};\n\n/** Aliased SEO result keys — excluded from generic field mapping. */\nconst SEO_ALIAS_COLUMNS = Object.values(SEO_COLUMN_ALIASES);\n\n/**\n * System columns excluded from entry.data\n * Note: slug is intentionally NOT excluded - it's useful as data.slug in templates\n */\nconst SYSTEM_COLUMNS = new Set([\n\t\"id\",\n\t// \"slug\" - kept in data for template access\n\t\"status\",\n\t\"author_id\",\n\t\"primary_byline_id\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t\"locale\",\n\t\"translation_group\",\n\t// Aliased SEO columns joined from _emdash_seo on the single-entry path.\n\t// Surfaced as a nested data.seo object (see extractSeo), never as flat\n\t// fields. The aliases are _emdash_-prefixed so they can't shadow a user\n\t// field named e.g. `seo_title`.\n\t...SEO_ALIAS_COLUMNS,\n]);\n\n/** Resolved SEO shape attached to `entry.data.seo`. Mirrors `ContentSeo`. */\ninterface EntrySeo {\n\ttitle: string | null;\n\tdescription: string | null;\n\timage: string | null;\n\tcanonical: string | null;\n\tnoIndex: boolean;\n}\n\n/**\n * Build a `data.seo` object from the joined `_emdash_seo` columns on a row.\n *\n * Returns `null` when no SEO row exists (LEFT JOIN miss → `seo_no_index` is\n * NULL, since the column is `NOT NULL DEFAULT 0` whenever a row is present).\n * Returning null keeps the `seo` key off entries that have none, so\n * `getSeoMeta()` falls back to its defaults exactly as before.\n */\nfunction extractSeo(row: Record<string, unknown>): EntrySeo | null {\n\tconst noIndex = row[SEO_COLUMN_ALIASES.seo_no_index];\n\tif (noIndex === null || noIndex === undefined) return null;\n\tconst title = row[SEO_COLUMN_ALIASES.seo_title];\n\tconst description = row[SEO_COLUMN_ALIASES.seo_description];\n\tconst image = row[SEO_COLUMN_ALIASES.seo_image];\n\tconst canonical = row[SEO_COLUMN_ALIASES.seo_canonical];\n\treturn {\n\t\ttitle: typeof title === \"string\" ? title : null,\n\t\tdescription: typeof description === \"string\" ? description : null,\n\t\timage: typeof image === \"string\" ? image : null,\n\t\tcanonical: typeof canonical === \"string\" ? canonical : null,\n\t\tnoIndex: noIndex === 1,\n\t};\n}\n\n/**\n * Get the table name for a collection type\n */\nfunction getTableName(type: string): string {\n\tvalidateIdentifier(type, \"collection type\");\n\treturn `ec_${type}`;\n}\n\n/**\n * Cache for taxonomy names (only used for the primary database).\n * Skipped when a per-request DB override is active (e.g. preview mode)\n * because the override DB may have different taxonomies.\n */\nlet taxonomyNames: Set<string> | null = null;\n\n/**\n * Get all taxonomy names (cached for the primary DB, bypassed only when\n * the per-request DB is an isolated instance — playground / DO preview).\n * Plain D1 Sessions routing shares schema with the singleton, so the\n * module-scoped cache stays valid.\n */\nasync function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {\n\tconst hasIsolatedDb = getRequestContext()?.dbIsIsolated === true;\n\n\tif (!hasIsolatedDb && taxonomyNames) {\n\t\treturn taxonomyNames;\n\t}\n\n\ttry {\n\t\tconst defs = await db.selectFrom(\"_emdash_taxonomy_defs\").select(\"name\").execute();\n\t\tconst names = new Set(defs.map((d) => d.name));\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = names;\n\t\t}\n\t\treturn names;\n\t} catch {\n\t\t// Table doesn't exist yet, return empty set\n\t\tconst empty = new Set<string>();\n\t\tif (!hasIsolatedDb) {\n\t\t\ttaxonomyNames = empty;\n\t\t}\n\t\treturn empty;\n\t}\n}\n\n/**\n * System columns to include in data (mapped to camelCase where needed)\n */\nconst INCLUDE_IN_DATA: Record<string, string> = {\n\tid: \"id\",\n\tstatus: \"status\",\n\tauthor_id: \"authorId\",\n\tprimary_byline_id: \"primaryBylineId\",\n\tcreated_at: \"createdAt\",\n\tupdated_at: \"updatedAt\",\n\tpublished_at: \"publishedAt\",\n\tscheduled_at: \"scheduledAt\",\n\tdraft_revision_id: \"draftRevisionId\",\n\tlive_revision_id: \"liveRevisionId\",\n\tlocale: \"locale\",\n\ttranslation_group: \"translationGroup\",\n};\n\n/** System date columns that should be converted to Date objects */\nconst DATE_COLUMNS = new Set([\"created_at\", \"updated_at\", \"published_at\", \"scheduled_at\"]);\n\n/**\n * Hidden, symbol-keyed property on each mapped data record carrying the raw\n * DB string for every date column. Lets cursor encoders downstream reproduce\n * the loader's exact `nextCursor` format without round-tripping through\n * `new Date()`, which loses precision for stored values that aren't already\n * ISO-with-milliseconds (e.g. `2026-01-01T00:00:00Z` becomes\n * `2026-01-01T00:00:00.000Z`).\n */\nexport const CURSOR_RAW_VALUES: unique symbol = Symbol(\"emdash:cursorRawValues\");\n\nconst LOCAL_MEDIA_FILE_PREFIX = \"/_emdash/api/media/file/\";\nconst URL_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/;\n\n/** Safely extract a string value from a record, returning fallback if not a string */\nfunction rowStr(row: Record<string, unknown>, key: string, fallback = \"\"): string {\n\tconst val = row[key];\n\treturn typeof val === \"string\" ? val : fallback;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isBareMediaKey(src: string): boolean {\n\treturn !src.startsWith(\"/\") && !URL_SCHEME_PATTERN.test(src);\n}\n\nfunction normalizeLocalMediaValue(value: unknown): unknown {\n\tif (Array.isArray(value)) {\n\t\treturn value.map(normalizeLocalMediaValue);\n\t}\n\n\tif (!isRecord(value)) {\n\t\treturn value;\n\t}\n\n\tconst normalized: Record<string, unknown> = {};\n\tfor (const [key, child] of Object.entries(value)) {\n\t\tnormalized[key] = normalizeLocalMediaValue(child);\n\t}\n\n\tif (\n\t\tnormalized.provider === \"local\" &&\n\t\ttypeof normalized.src === \"string\" &&\n\t\tnormalized.src.length > 0\n\t) {\n\t\tconst src = normalized.src;\n\t\tif (src.startsWith(LOCAL_MEDIA_FILE_PREFIX)) {\n\t\t\tconst id = src.slice(LOCAL_MEDIA_FILE_PREFIX.length);\n\t\t\tif (!normalized.id && id) {\n\t\t\t\tnormalized.id = id;\n\t\t\t}\n\t\t} else if (isBareMediaKey(src)) {\n\t\t\tif (!normalized.id) {\n\t\t\t\tnormalized.id = src;\n\t\t\t}\n\t\t\tnormalized.src = `${LOCAL_MEDIA_FILE_PREFIX}${src}`;\n\t\t}\n\t}\n\n\treturn normalized;\n}\n\n/**\n * Map a database row to entry data\n * Extracts content fields (non-system columns) and parses JSON where needed.\n * System columns needed for templates (id, status, dates) are included with camelCase names.\n */\nfunction mapRowToData(row: Record<string, unknown>): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {};\n\tconst rawDateValues: Record<string, string> = {};\n\n\tfor (const [key, value] of Object.entries(row)) {\n\t\t// Include certain system columns (mapped to camelCase where needed)\n\t\tif (key in INCLUDE_IN_DATA) {\n\t\t\t// Convert date columns from ISO strings to Date objects\n\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\tif (typeof value === \"string\") {\n\t\t\t\t\trawDateValues[key] = value;\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = new Date(value);\n\t\t\t\t} else {\n\t\t\t\t\tdata[INCLUDE_IN_DATA[key]] = null;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata[INCLUDE_IN_DATA[key]] = value;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (SYSTEM_COLUMNS.has(key)) continue;\n\n\t\t// Try to parse JSON strings (for portableText, json fields, etc.)\n\t\tif (typeof value === \"string\") {\n\t\t\ttry {\n\t\t\t\t// Only parse if it looks like JSON (starts with { or [)\n\t\t\t\tif (value.startsWith(\"{\") || value.startsWith(\"[\")) {\n\t\t\t\t\tdata[key] = normalizeLocalMediaValue(JSON.parse(value));\n\t\t\t\t} else {\n\t\t\t\t\tdata[key] = value;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tdata[key] = value;\n\t\t\t}\n\t\t} else {\n\t\t\tdata[key] = value;\n\t\t}\n\t}\n\n\tObject.defineProperty(data, CURSOR_RAW_VALUES, {\n\t\tvalue: rawDateValues,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t});\n\n\treturn data;\n}\n\n/**\n * Map revision data (already-parsed JSON object) to entry data.\n * Strips _-prefixed metadata keys (e.g. _slug) used internally by revisions.\n */\nfunction mapRevisionData(data: Record<string, unknown>): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (key.startsWith(\"_\")) continue; // revision metadata\n\t\tresult[key] = normalizeLocalMediaValue(value);\n\t}\n\treturn result;\n}\n\n// Virtual module imports are lazy-loaded to avoid errors when importing\n// emdash outside of Astro/Vite context (e.g., in astro.config.mjs)\nlet virtualConfig:\n\t| {\n\t\t\tdatabase?: { config: unknown };\n\t\t\ti18n?: { defaultLocale: string; locales: string[]; prefixDefaultLocale?: boolean } | null;\n\t }\n\t| undefined;\nlet virtualCreateDialect: ((config: unknown) => Dialect) | undefined;\n\nasync function loadVirtualModules() {\n\tif (virtualConfig === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst configModule = await import(\"virtual:emdash/config\");\n\t\tvirtualConfig = configModule.default;\n\t}\n\tif (virtualCreateDialect === undefined) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore - virtual module\n\t\tconst dialectModule = await import(\"virtual:emdash/dialect\");\n\t\tvirtualCreateDialect = dialectModule.createDialect;\n\t\t// dialectType is no longer needed here — dialect detection is\n\t\t// done via the db adapter instance in dialect-helpers.ts\n\t}\n}\n\n/**\n * Entry data type - generic object\n */\nexport type EntryData = Record<string, unknown>;\n\n/**\n * Sort direction\n */\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Order by specification - field name to direction\n * @example { created_at: \"desc\" } - Sort by created_at descending\n * @example { title: \"asc\" } - Sort by title ascending\n */\nexport type OrderBySpec = Record<string, SortDirection>;\n\n/**\n * Build WHERE clause for status filtering.\n * When filtering for 'published' status, also include scheduled content\n * whose scheduled_at time has passed (treating it as effectively published).\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nfunction buildStatusCondition(\n\tdb: Kysely<any>,\n\tstatus: string,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst statusField = tablePrefix ? `${tablePrefix}.status` : \"status\";\n\tconst scheduledAtField = tablePrefix ? `${tablePrefix}.scheduled_at` : \"scheduled_at\";\n\n\tif (status === \"published\") {\n\t\t// Include both published content AND scheduled content past its publish time.\n\t\t// scheduled_at is stored as text (ISO 8601). On Postgres, we must cast it\n\t\t// to timestamptz for the comparison with CURRENT_TIMESTAMP to work.\n\t\tconst scheduledAtExpr = isPostgres(db)\n\t\t\t? sql`${sql.ref(scheduledAtField)}::timestamptz`\n\t\t\t: sql.ref(scheduledAtField);\n\t\tconst nowExpr = isPostgres(db)\n\t\t\t? currentTimestampValue(db)\n\t\t\t: sql`strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n\t\treturn sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${nowExpr}))`;\n\t}\n\n\treturn sql`${sql.ref(statusField)} = ${status}`;\n}\n\n/**\n * Resolved primary sort field and direction (used for cursor pagination).\n */\ninterface PrimarySort {\n\tfield: string;\n\tdirection: SortDirection;\n}\n\n/**\n * Get the primary sort field from an orderBy spec (first valid field, or default).\n */\nfunction getPrimarySort(orderBy: OrderBySpec | undefined, tablePrefix?: string): PrimarySort {\n\tif (orderBy) {\n\t\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t\tif (FIELD_NAME_PATTERN.test(field)) {\n\t\t\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\t\t\treturn { field: fullField, direction };\n\t\t\t}\n\t\t}\n\t}\n\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\treturn { field: defaultField, direction: \"desc\" };\n}\n\n/**\n * Build ORDER BY clause from orderBy spec\n * Validates field names to prevent SQL injection (alphanumeric + underscore only)\n * Supports multiple sort fields in object key order\n */\nfunction buildOrderByClause(\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\t// Default to created_at DESC\n\tif (!orderBy || Object.keys(orderBy).length === 0) {\n\t\tconst field = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(field)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\tconst sortParts: ReturnType<typeof sql>[] = [];\n\n\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t// Validate field name (alphanumeric + underscore only)\n\t\tif (!FIELD_NAME_PATTERN.test(field)) {\n\t\t\tcontinue; // Skip invalid field names\n\t\t}\n\n\t\tconst fullField = tablePrefix ? `${tablePrefix}.${field}` : field;\n\t\tconst dir = direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\t\tsortParts.push(sql`${sql.ref(fullField)} ${dir}`);\n\t}\n\n\t// If no valid sort fields, fall back to default\n\tif (sortParts.length === 0) {\n\t\tconst defaultField = tablePrefix ? `${tablePrefix}.created_at` : \"created_at\";\n\t\treturn sql`ORDER BY ${sql.ref(defaultField)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : \"id\")} DESC`;\n\t}\n\n\t// Add id as tiebreaker to ensure stable cursor ordering\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\tconst idDir = primary.direction === \"asc\" ? sql`ASC` : sql`DESC`;\n\tsortParts.push(sql`${sql.ref(idField)} ${idDir}`);\n\n\treturn sql`ORDER BY ${sql.join(sortParts, sql`, `)}`;\n}\n\n/**\n * Build a cursor WHERE condition for keyset pagination.\n * Uses the primary sort field + id as tiebreaker for stable ordering.\n *\n * Throws `InvalidCursorError` if the cursor is malformed; callers should\n * let this propagate so users see a real error rather than silently\n * falling back to the first page.\n */\nfunction buildCursorCondition(\n\tcursor: string,\n\torderBy: OrderBySpec | undefined,\n\ttablePrefix?: string,\n): ReturnType<typeof sql> {\n\tconst { orderValue, id: cursorId } = decodeCursor(cursor);\n\tconst primary = getPrimarySort(orderBy, tablePrefix);\n\tconst idField = tablePrefix ? `${tablePrefix}.id` : \"id\";\n\n\tif (primary.direction === \"desc\") {\n\t\treturn sql`(${sql.ref(primary.field)} < ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} < ${cursorId}))`;\n\t}\n\treturn sql`(${sql.ref(primary.field)} > ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} > ${cursorId}))`;\n}\n\n/** Type guard: is the where value a range object (not a string or array)? */\nfunction isWhereRange(value: WhereValue): value is WhereRange {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * Build AND conditions for non-taxonomy field filters.\n * Returns an array of sql fragments; empty if no field filters apply.\n * Field names are validated against FIELD_NAME_PATTERN to prevent injection.\n */\nfunction buildFieldConditions(\n\tfields: Record<string, WhereValue>,\n\ttablePrefix?: string,\n): ReturnType<typeof sql>[] {\n\tconst conditions: ReturnType<typeof sql>[] = [];\n\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (!FIELD_NAME_PATTERN.test(key)) {\n\t\t\tconsole.warn(`[emdash] where filter: invalid field name \"${key}\" ignored`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (value == null) continue;\n\t\tconst ref = tablePrefix ? sql.ref(`${tablePrefix}.${key}`) : sql.ref(key);\n\n\t\tif (isWhereRange(value)) {\n\t\t\tif (value.gt !== undefined) conditions.push(sql`${ref} > ${value.gt}`);\n\t\t\tif (value.gte !== undefined) conditions.push(sql`${ref} >= ${value.gte}`);\n\t\t\tif (value.lt !== undefined) conditions.push(sql`${ref} < ${value.lt}`);\n\t\t\tif (value.lte !== undefined) conditions.push(sql`${ref} <= ${value.lte}`);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tif (value.length > 0) {\n\t\t\t\tconditions.push(sql`${ref} IN (${sql.join(value.map((v) => sql`${v}`))})`);\n\t\t\t}\n\t\t} else {\n\t\t\tconditions.push(sql`${ref} = ${value}`);\n\t\t}\n\t}\n\n\treturn conditions;\n}\n\n/**\n * Range filter for comparison operators on field values.\n * Values are compared as strings in the database. This works correctly for\n * ISO 8601 dates (e.g. \"2024-01-01T00:00:00Z\") because lexicographic ordering\n * matches chronological ordering. Ensure date values use a consistent format.\n */\nexport interface WhereRange {\n\tgt?: string;\n\tgte?: string;\n\tlt?: string;\n\tlte?: string;\n}\n\n/**\n * A where clause value: exact match, multi-value match, or range comparison.\n */\nexport type WhereValue = string | string[] | WhereRange;\n\n/**\n * Filter for loadCollection - type is required\n */\nexport interface CollectionFilter {\n\ttype: string;\n\tstatus?: \"draft\" | \"published\" | \"archived\";\n\tlimit?: number;\n\t/**\n\t * Opaque cursor for keyset pagination.\n\t * Pass the `nextCursor` value from a previous result to fetch the next page.\n\t */\n\tcursor?: string;\n\t/**\n\t * Filter by field values, taxonomy terms, byline credits, or ranges.\n\t *\n\t * Taxonomy names are detected automatically and filtered via JOIN.\n\t * The reserved `byline` key filters by byline credit (any credit, not\n\t * just the primary one) via the `_emdash_content_bylines` junction\n\t * table; its value is one or more byline translation groups.\n\t * Other keys are treated as column filters on the content table.\n\t *\n\t * @example { category: 'news' } - taxonomy term\n\t * @example { byline: '01HXYZ...' } - entries credited to a byline (any position)\n\t * @example { series: 'main' } - exact match on a content field\n\t * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - date range\n\t */\n\twhere?: Record<string, WhereValue>;\n\t/**\n\t * Order results by field(s)\n\t * @default { created_at: \"desc\" }\n\t */\n\torderBy?: OrderBySpec;\n\t/**\n\t * Filter by locale (e.g. 'en', 'fr').\n\t * When set, only returns content in this locale.\n\t */\n\tlocale?: string;\n}\n\n/**\n * Filter for loadEntry - type and id are required\n */\nexport interface EntryFilter {\n\ttype: string;\n\tid: string;\n\t/**\n\t * When set, fetch content data from this revision instead of the content table.\n\t * Used by preview mode to serve draft revision data.\n\t */\n\trevisionId?: string;\n\t/**\n\t * Locale to scope slug lookup. Only affects slug resolution;\n\t * IDs are globally unique and always resolve regardless of locale.\n\t */\n\tlocale?: string;\n}\n\n// Cached database instance (shared across calls)\nlet dbInstance: Kysely<Database> | null = null;\n\n/**\n * Get the database instance. Used by query wrapper functions and middleware.\n *\n * Checks the ALS request context first — if a per-request DB override is set\n * (e.g. by DO preview middleware), it takes precedence over the module-level\n * cached instance. This allows preview mode to route queries to an isolated\n * Durable Object database without modifying any calling code.\n *\n * Initializes the default database on first call using config from virtual module.\n */\nexport async function getDb(): Promise<Kysely<Database>> {\n\t// Per-request DB override via ALS (normal mode)\n\tconst ctx = getRequestContext();\n\tif (ctx?.db) {\n\t\treturn ctx.db as Kysely<Database>; // eslint-disable-line typescript/no-unsafe-type-assertion -- db is typed as unknown in RequestContext to avoid circular deps\n\t}\n\n\tif (!dbInstance) {\n\t\tawait loadVirtualModules();\n\t\tif (!virtualConfig?.database || typeof virtualCreateDialect !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"EmDash database not configured. Add database config to emdash() in astro.config.mjs\",\n\t\t\t);\n\t\t}\n\t\tconst dialect = virtualCreateDialect(virtualConfig.database.config);\n\t\tdbInstance = new Kysely<Database>({ dialect, log: kyselyLogOption() });\n\t}\n\treturn dbInstance;\n}\n\n/**\n * Create an EmDash Live Collections loader\n *\n * This loader handles ALL content types in a single Astro collection.\n * Use `getEmDashCollection()` and `getEmDashEntry()` to query\n * specific content types.\n *\n * Database is configured in astro.config.mjs via the emdash() integration.\n *\n * @example\n * ```ts\n * // src/live.config.ts\n * import { defineLiveCollection } from \"astro:content\";\n * import { emdashLoader } from \"emdash\";\n *\n * export const collections = {\n * emdash: defineLiveCollection({\n * loader: emdashLoader(),\n * }),\n * };\n * ```\n */\nexport function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFilter> {\n\treturn {\n\t\tname: \"emdash\",\n\n\t\t/**\n\t\t * Load all entries for a content type\n\t\t */\n\t\tasync loadCollection({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance (initializes on first use)\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Type filter is required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tif (!type) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type filter is required. Use getEmDashCollection() instead of getLiveCollection() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table (ec_posts, ec_products, etc.)\n\t\t\t\tconst tableName = getTableName(type);\n\n\t\t\t\t// Build query with dynamic table name\n\t\t\t\tconst status = filter?.status || \"published\";\n\t\t\t\tconst limit = filter?.limit;\n\t\t\t\tconst cursor = filter?.cursor;\n\t\t\t\tconst where = filter?.where;\n\t\t\t\tconst orderBy = filter?.orderBy;\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Cursor pagination: over-fetch by 1 to detect next page\n\t\t\t\tconst fetchLimit = limit ? limit + 1 : undefined;\n\n\t\t\t\t// Build cursor condition if cursor is provided\n\t\t\t\tconst cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;\n\n\t\t\t\t// Separate taxonomy / byline filters from field filters\n\t\t\t\tlet result: { rows: Record<string, unknown>[] };\n\t\t\t\t// Taxonomy filters AND together: each entry constrains the base\n\t\t\t\t// row to match at least one of its slugs *within that taxonomy*.\n\t\t\t\t// Term slugs are unique only within a taxonomy, so every filter\n\t\t\t\t// keeps its own `name` and emits its own `EXISTS` clause rather\n\t\t\t\t// than pooling slugs into one `IN`.\n\t\t\t\tconst taxonomyFilters: { name: string; slugs: string[] }[] = [];\n\t\t\t\t// A byline filter matches entries credited to any of the given\n\t\t\t\t// byline translation groups via the `_emdash_content_bylines`\n\t\t\t\t// junction table. `null` means no byline filter; an empty\n\t\t\t\t// `groups` array means the filter was requested but matches\n\t\t\t\t// nothing (short-circuited to an empty result below).\n\t\t\t\tlet bylineFilter: { groups: string[] } | null = null;\n\t\t\t\tconst fieldFilters: Record<string, WhereValue> = {};\n\n\t\t\t\tif (where && Object.keys(where).length > 0) {\n\t\t\t\t\tconst taxNames = await getTaxonomyNames(db);\n\n\t\t\t\t\tfor (const [key, value] of Object.entries(where)) {\n\t\t\t\t\t\tif (value == null) continue;\n\t\t\t\t\t\tif (key === \"byline\") {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on \"byline\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst groups = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\tbylineFilter = { groups };\n\t\t\t\t\t\t} else if (taxNames.has(key)) {\n\t\t\t\t\t\t\tif (isWhereRange(value)) {\n\t\t\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\t\t`[emdash] where filter: range operators are not supported on taxonomy \"${key}\", ignored`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst slugs = Array.isArray(value) ? value : [value];\n\t\t\t\t\t\t\ttaxonomyFilters.push({ name: key, slugs });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfieldFilters[key] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A byline or taxonomy filter with no values matches nothing —\n\t\t\t\t// short-circuit before building SQL (an empty `IN ()` is invalid\n\t\t\t\t// SQL on both dialects).\n\t\t\t\tif (\n\t\t\t\t\t(bylineFilter && bylineFilter.groups.length === 0) ||\n\t\t\t\t\ttaxonomyFilters.some((f) => f.slugs.length === 0)\n\t\t\t\t) {\n\t\t\t\t\treturn { entries: [], cacheHint: { tags: [type] } };\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\t// Taxonomy and byline filters are applied as correlated\n\t\t\t\t\t// `EXISTS` semi-joins rather than `INNER JOIN ... DISTINCT`.\n\t\t\t\t\t// A join fan-out would force `SELECT DISTINCT table.*`, and\n\t\t\t\t\t// Postgres cannot apply DISTINCT to a row containing a `json`\n\t\t\t\t\t// column (no equality operator), so the join approach throws\n\t\t\t\t\t// there. EXISTS matches \"credited/tagged at least once\"\n\t\t\t\t\t// without duplicating rows, needs no DISTINCT, and works on\n\t\t\t\t\t// both SQLite and Postgres. The base query stays a single-\n\t\t\t\t\t// table `SELECT *`, so all field/status/locale/cursor/order\n\t\t\t\t\t// conditions reference unprefixed columns as before.\n\t\t\t\t\tconst orderByClause = buildOrderByClause(orderBy);\n\t\t\t\t\tconst statusCondition = buildStatusCondition(db, status);\n\t\t\t\t\tconst localeFilter = locale ? sql`AND locale = ${locale}` : sql``;\n\t\t\t\t\tconst cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;\n\t\t\t\t\tconst fieldConds = buildFieldConditions(fieldFilters);\n\t\t\t\t\tconst fieldCondsSQL =\n\t\t\t\t\t\tfieldConds.length > 0 ? sql`${sql.join(fieldConds, sql` AND `)}` : null;\n\n\t\t\t\t\t// One `EXISTS` per taxonomy, AND'd together: an entry must be\n\t\t\t\t\t// tagged with a matching term in *every* requested taxonomy.\n\t\t\t\t\t// Each clause pins its own `t.name`, so slugs never pool\n\t\t\t\t\t// across taxonomies (they're only unique within one).\n\t\t\t\t\tconst taxonomyCond =\n\t\t\t\t\t\ttaxonomyFilters.length > 0\n\t\t\t\t\t\t\t? sql`${sql.join(\n\t\t\t\t\t\t\t\t\ttaxonomyFilters.map(\n\t\t\t\t\t\t\t\t\t\t(f) => sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM content_taxonomies ct\n\t\t\t\t\t\t\tINNER JOIN taxonomies t ON t.id = ct.taxonomy_id\n\t\t\t\t\t\t\tWHERE ct.collection = ${type}\n\t\t\t\t\t\t\t\tAND ct.entry_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND t.name = ${f.name}\n\t\t\t\t\t\t\t\tAND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})\n\t\t\t\t\t\t)`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tsql` `,\n\t\t\t\t\t\t\t\t)}`\n\t\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\t// `_emdash_content_bylines.byline_id` stores the byline's\n\t\t\t\t\t// translation_group (migration 040), so a credit spans every\n\t\t\t\t\t// locale variant of the byline and we match the group directly.\n\t\t\t\t\tconst bylineCond = bylineFilter\n\t\t\t\t\t\t? sql`AND EXISTS (\n\t\t\t\t\t\t\tSELECT 1 FROM _emdash_content_bylines cb\n\t\t\t\t\t\t\tWHERE cb.collection_slug = ${type}\n\t\t\t\t\t\t\t\tAND cb.content_id = ${sql.ref(tableName)}.id\n\t\t\t\t\t\t\t\tAND cb.byline_id IN (${sql.join(bylineFilter.groups.map((g) => sql`${g}`))})\n\t\t\t\t\t\t)`\n\t\t\t\t\t\t: sql``;\n\n\t\t\t\t\tresult = await sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t\tAND ${statusCondition}\n\t\t\t\t\t\t${localeFilter}\n\t\t\t\t\t\t${cursorCond}\n\t\t\t\t\t\t${taxonomyCond}\n\t\t\t\t\t\t${bylineCond}\n\t\t\t\t\t\t${fieldCondsSQL ? sql`AND ${fieldCondsSQL}` : sql``}\n\t\t\t\t\t\t${orderByClause}\n\t\t\t\t\t\t${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}\n\t\t\t\t\t`.execute(db);\n\t\t\t\t}\n\n\t\t\t\t// Detect whether there are more results (over-fetched by 1)\n\t\t\t\tconst hasMore = limit ? result.rows.length > limit : false;\n\t\t\t\tconst rows = hasMore ? result.rows.slice(0, limit) : result.rows;\n\n\t\t\t\t// Map rows to entries\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entries = rows.map((row) => {\n\t\t\t\t\tconst slug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\t\tconst rowLocale = rowStr(row, \"locale\");\n\t\t\t\t\tconst shouldPrefix =\n\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\trowLocale !== \"\" &&\n\t\t\t\t\t\t(rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\tconst id = shouldPrefix ? `${rowLocale}/${slug}` : slug;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\tdata: mapRowToData(row),\n\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Encode nextCursor from the last row if there are more results\n\t\t\t\tlet nextCursor: string | undefined;\n\t\t\t\tif (hasMore && rows.length > 0) {\n\t\t\t\t\tconst lastRow = rows.at(-1)!;\n\t\t\t\t\tconst primary = getPrimarySort(orderBy);\n\t\t\t\t\t// Strip table prefix from field name for row lookup\n\t\t\t\t\tconst fieldName = primary.field.includes(\".\")\n\t\t\t\t\t\t? primary.field.split(\".\").pop()!\n\t\t\t\t\t\t: primary.field;\n\t\t\t\t\tconst lastOrderValue = lastRow[fieldName];\n\t\t\t\t\tconst orderStr =\n\t\t\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tnextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t\t\t}\n\n\t\t\t\t// Collection-level cache hint uses the most recent updated_at\n\t\t\t\tlet collectionLastModified: Date | undefined;\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tif (row.updated_at) {\n\t\t\t\t\t\tconst d = new Date(rowStr(row, \"updated_at\"));\n\t\t\t\t\t\tif (!collectionLastModified || d > collectionLastModified) {\n\t\t\t\t\t\t\tcollectionLastModified = d;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tentries,\n\t\t\t\t\tnextCursor,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [type],\n\t\t\t\t\t\tlastModified: collectionLastModified,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table/column gracefully - return empty collection.\n\t\t\t\t// Missing table happens before migrations have run.\n\t\t\t\t// Missing column happens when a where filter references a non-existent field.\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (isMissingTableError(error) || isMissingColumnError(error)) {\n\t\t\t\t\tif (isMissingColumnError(error)) {\n\t\t\t\t\t\tconsole.warn(`[emdash] where filter: ${message}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn { entries: [] };\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load collection: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Load a single entry by type and ID/slug\n\t\t *\n\t\t * When filter.revisionId is set (preview mode), the entry's data\n\t\t * comes from the revisions table instead of the content table columns.\n\t\t */\n\t\tasync loadEntry({ filter }) {\n\t\t\ttry {\n\t\t\t\t// Get DB instance\n\t\t\t\tconst db = await getDb();\n\n\t\t\t\t// Both type and id are required\n\t\t\t\tconst type = filter?.type;\n\t\t\t\tconst id = filter?.id;\n\n\t\t\t\tif (!type || !id) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: new Error(\n\t\t\t\t\t\t\t\"type and id filters are required. Use getEmDashEntry() instead of getLiveEntry() directly.\",\n\t\t\t\t\t\t),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Query the per-collection table\n\t\t\t\tconst tableName = getTableName(type);\n\t\t\t\tconst locale = filter?.locale;\n\n\t\t\t\t// Use raw SQL for dynamic table name, match by slug or id\n\t\t\t\t// When locale is specified, prefer locale-scoped slug match,\n\t\t\t\t// but IDs are globally unique so always check id without locale scope.\n\t\t\t\t//\n\t\t\t\t// LEFT JOIN _emdash_seo folds per-entry SEO (canonical, noindex,\n\t\t\t\t// etc.) into this single query at zero extra round-trip cost. The\n\t\t\t\t// joined columns are surfaced as a nested data.seo object via\n\t\t\t\t// extractSeo() and excluded from the generic field mapping. SEO is\n\t\t\t\t// 1:1 with content (PK on collection+content_id), so the join never\n\t\t\t\t// multiplies rows.\n\t\t\t\tconst seoSelect = sql.join(\n\t\t\t\t\tObject.entries(SEO_COLUMN_ALIASES).map(\n\t\t\t\t\t\t([col, alias]) => sql`${sql.ref(`s.${col}`)} AS ${sql.ref(alias)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst result = locale\n\t\t\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tLEFT JOIN ${sql.ref(\"_emdash_seo\")} AS s\n\t\t\t\t\t\t\t\tON s.collection = ${type} AND s.content_id = c.id\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND ((c.slug = ${id} AND c.locale = ${locale}) OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db)\n\t\t\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\t\t\tSELECT c.*, ${seoSelect}\n\t\t\t\t\t\t\tFROM ${sql.ref(tableName)} AS c\n\t\t\t\t\t\t\tLEFT JOIN ${sql.ref(\"_emdash_seo\")} AS s\n\t\t\t\t\t\t\t\tON s.collection = ${type} AND s.content_id = c.id\n\t\t\t\t\t\t\tWHERE c.deleted_at IS NULL\n\t\t\t\t\t\t\tAND (c.slug = ${id} OR c.id = ${id})\n\t\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t\t`.execute(db);\n\n\t\t\t\tconst row = result.rows[0];\n\t\t\t\tif (!row) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst i18nConfig = virtualConfig?.i18n;\n\t\t\t\tconst i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;\n\t\t\t\tconst entrySlug = rowStr(row, \"slug\") || rowStr(row, \"id\");\n\t\t\t\tconst entryLocale = rowStr(row, \"locale\");\n\t\t\t\tconst shouldPrefixEntry =\n\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\tentryLocale !== \"\" &&\n\t\t\t\t\t(entryLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\tconst entryId = shouldPrefixEntry ? `${entryLocale}/${entrySlug}` : entrySlug;\n\n\t\t\t\t// Preview mode: override content fields with revision data,\n\t\t\t\t// keeping system metadata from the content table row.\n\t\t\t\tconst revisionId = filter?.revisionId;\n\t\t\t\tif (revisionId) {\n\t\t\t\t\tconst revRow = await sql<{ data: string }>`\n\t\t\t\t\t\tSELECT data FROM revisions\n\t\t\t\t\t\tWHERE id = ${revisionId}\n\t\t\t\t\t\tLIMIT 1\n\t\t\t\t\t`.execute(db);\n\n\t\t\t\t\tconst revData = revRow.rows[0];\n\t\t\t\t\tif (revData) {\n\t\t\t\t\t\tconst parsed: Record<string, unknown> = JSON.parse(revData.data);\n\t\t\t\t\t\t// System metadata from content table + content fields from revision\n\t\t\t\t\t\tconst systemData: Record<string, unknown> = {};\n\t\t\t\t\t\tfor (const [key, mappedKey] of Object.entries(INCLUDE_IN_DATA)) {\n\t\t\t\t\t\t\tif (key in row) {\n\t\t\t\t\t\t\t\tif (DATE_COLUMNS.has(key)) {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = typeof row[key] === \"string\" ? new Date(row[key]) : null;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tsystemData[mappedKey] = row[key];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Use slug from revision metadata if present, else from content table\n\t\t\t\t\t\tconst slug = typeof parsed._slug === \"string\" ? parsed._slug : rowStr(row, \"slug\");\n\t\t\t\t\t\tconst revSlug = slug || rowStr(row, \"id\");\n\t\t\t\t\t\tconst revLocale = rowStr(row, \"locale\");\n\t\t\t\t\t\tconst shouldPrefixRev =\n\t\t\t\t\t\t\ti18nEnabled &&\n\t\t\t\t\t\t\trevLocale !== \"\" &&\n\t\t\t\t\t\t\t(revLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);\n\t\t\t\t\t\tconst revId = shouldPrefixRev ? `${revLocale}/${revSlug}` : revSlug;\n\t\t\t\t\t\t// SEO is not revisioned — it comes from the content row's\n\t\t\t\t\t\t// joined _emdash_seo columns, not the revision snapshot.\n\t\t\t\t\t\tconst revEntryData: Record<string, unknown> = {\n\t\t\t\t\t\t\t...systemData,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\t...mapRevisionData(parsed),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst revSeo = extractSeo(row);\n\t\t\t\t\t\tif (revSeo) revEntryData.seo = revSeo;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: revId,\n\t\t\t\t\t\t\tslug,\n\t\t\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\t\t\tdata: revEntryData,\n\t\t\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst entryData = mapRowToData(row);\n\t\t\t\tconst entrySeo = extractSeo(row);\n\t\t\t\tif (entrySeo) entryData.seo = entrySeo;\n\t\t\t\treturn {\n\t\t\t\t\tid: entryId,\n\t\t\t\t\tslug: rowStr(row, \"slug\"),\n\t\t\t\t\tstatus: rowStr(row, \"status\", \"draft\"),\n\t\t\t\t\tdata: entryData,\n\t\t\t\t\tcacheHint: {\n\t\t\t\t\t\ttags: [rowStr(row, \"id\")],\n\t\t\t\t\t\tlastModified: row.updated_at ? new Date(rowStr(row, \"updated_at\")) : undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// Handle missing table gracefully - return undefined (not found).\n\t\t\t\t// This happens before migrations have run.\n\t\t\t\tif (isMissingTableError(error)) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn {\n\t\t\t\t\terror: new Error(`Failed to load entry: ${message}`),\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -279,4 +279,4 @@ function normalizeManifestRoute(entry) {
279
279
 
280
280
  //#endregion
281
281
  export { reconcileManifestAccess as a, pluginManifestSchema as i, PLUGIN_CAPABILITIES as n, normalizeManifestRoute as r, HOOK_NAMES as t };
282
- //# sourceMappingURL=manifest-schema-BtwbL_vj.mjs.map
282
+ //# sourceMappingURL=manifest-schema-DFPeqMAn.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-schema-BtwbL_vj.mjs","names":[],"sources":["../src/plugins/manifest-schema.ts"],"sourcesContent":["/**\n * Zod schema for PluginManifest validation\n *\n * Used to validate manifest.json from plugin bundles at every parse site:\n * - Client-side download (marketplace.ts extractBundle)\n * - R2 load (api/handlers/marketplace.ts loadBundleFromR2)\n * - CLI publish preview (cli/commands/publish.ts readManifestFromTarball)\n * - Marketplace ingest extends this with publishing-specific fields\n */\n\nimport {\n\tcapabilitiesToDeclaredAccess,\n\tdeclaredAccessToCapabilities,\n} from \"@emdash-cms/plugin-types\";\nimport { z } from \"zod\";\n\nimport type { PluginManifest } from \"./types.js\";\n\n// ── Enum values (must stay in sync with types.ts) ───────────────\n\n/**\n * Current capability names — the ones authors should use going forward.\n * See `PluginCapability` in `types.ts` for documentation of each.\n */\nexport const CURRENT_PLUGIN_CAPABILITIES = [\n\t\"network:request\",\n\t\"network:request:unrestricted\",\n\t\"content:read\",\n\t\"content:write\",\n\t\"media:read\",\n\t\"media:write\",\n\t\"users:read\",\n\t\"email:send\",\n\t\"hooks.email-transport:register\",\n\t\"hooks.email-events:register\",\n\t\"hooks.page-fragments:register\",\n] as const;\n\n/**\n * Legacy capability names accepted during the deprecation window.\n * Normalized to current names via `normalizeCapability()` in types.ts\n * before reaching the runtime. Plugin authors are warned at bundle/validate\n * and hard-failed at publish.\n */\nexport const DEPRECATED_PLUGIN_CAPABILITIES = [\n\t\"network:fetch\",\n\t\"network:fetch:any\",\n\t\"read:content\",\n\t\"write:content\",\n\t\"read:media\",\n\t\"write:media\",\n\t\"read:users\",\n\t\"email:provide\",\n\t\"email:intercept\",\n\t\"page:inject\",\n] as const;\n\n/**\n * Full set of accepted capability strings — current + deprecated.\n *\n * The manifest schema accepts both during the transition. The runtime only\n * ever sees current names because `normalizeCapability()` rewrites legacy\n * names at every external boundary (definePlugin, adaptSandboxEntry).\n */\nexport const PLUGIN_CAPABILITIES = [\n\t...CURRENT_PLUGIN_CAPABILITIES,\n\t...DEPRECATED_PLUGIN_CAPABILITIES,\n] as const;\n\n/** Must stay in sync with FieldType in schema/types.ts */\nconst FIELD_TYPES = [\n\t\"string\",\n\t\"text\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"multiSelect\",\n\t\"portableText\",\n\t\"image\",\n\t\"file\",\n\t\"reference\",\n\t\"json\",\n\t\"slug\",\n\t\"repeater\",\n] as const;\n\nexport const HOOK_NAMES = [\n\t\"plugin:install\",\n\t\"plugin:activate\",\n\t\"plugin:deactivate\",\n\t\"plugin:uninstall\",\n\t\"content:beforeSave\",\n\t\"content:afterSave\",\n\t\"content:beforeDelete\",\n\t\"content:afterDelete\",\n\t\"content:afterPublish\",\n\t\"content:afterUnpublish\",\n\t\"media:beforeUpload\",\n\t\"media:afterUpload\",\n\t\"cron\",\n\t\"email:beforeSend\",\n\t\"email:deliver\",\n\t\"email:afterSend\",\n\t\"comment:beforeCreate\",\n\t\"comment:moderate\",\n\t\"comment:afterCreate\",\n\t\"comment:afterModerate\",\n\t\"page:metadata\",\n\t\"page:fragments\",\n] as const;\n\n/**\n * Structured hook entry for manifest — name plus optional metadata.\n * During a transition period, both plain strings and objects are accepted.\n */\nconst manifestHookEntrySchema = z.object({\n\tname: z.enum(HOOK_NAMES),\n\texclusive: z.boolean().optional(),\n\tpriority: z.number().int().optional(),\n\ttimeout: z.number().int().positive().optional(),\n});\n\n/**\n * Structured route entry for manifest — name plus optional metadata.\n * Both plain strings and objects are accepted; strings are normalized\n * to `{ name }` objects via `normalizeManifestRoute()`.\n */\n/** Route names must be safe path segments — alphanumeric, hyphens, underscores, forward slashes */\nconst routeNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9_\\-/]*$/;\n\nconst manifestRouteEntrySchema = z.object({\n\tname: z.string().min(1).regex(routeNamePattern, \"Route name must be a safe path segment\"),\n\tpublic: z.boolean().optional(),\n});\n\n// ── Sub-schemas ─────────────────────────────────────────────────\n\n/** Index field names must be valid identifiers to prevent SQL injection via JSON path expressions */\nconst indexFieldName = z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]*$/);\n\nconst storageCollectionSchema = z.object({\n\tindexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])),\n\tuniqueIndexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])).optional(),\n});\n\nconst baseSettingFields = {\n\tlabel: z.string(),\n\tdescription: z.string().optional(),\n};\n\nconst settingFieldSchema = z.discriminatedUnion(\"type\", [\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"string\"),\n\t\tdefault: z.string().optional(),\n\t\tmultiline: z.boolean().optional(),\n\t}),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"number\"),\n\t\tdefault: z.number().optional(),\n\t\tmin: z.number().optional(),\n\t\tmax: z.number().optional(),\n\t}),\n\tz.object({ ...baseSettingFields, type: z.literal(\"boolean\"), default: z.boolean().optional() }),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"select\"),\n\t\toptions: z.array(z.object({ value: z.string(), label: z.string() })),\n\t\tdefault: z.string().optional(),\n\t}),\n\tz.object({ ...baseSettingFields, type: z.literal(\"secret\") }),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"url\"),\n\t\tdefault: z.string().optional(),\n\t\tplaceholder: z.string().optional(),\n\t}),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"email\"),\n\t\tdefault: z.string().optional(),\n\t\tplaceholder: z.string().optional(),\n\t}),\n]);\n\nconst adminPageSchema = z.object({\n\tpath: z.string(),\n\tlabel: z.string(),\n\ticon: z.string().optional(),\n});\n\nconst dashboardWidgetSchema = z.object({\n\tid: z.string(),\n\tsize: z.enum([\"full\", \"half\", \"third\"]).optional(),\n\ttitle: z.string().optional(),\n});\n\nconst pluginAdminConfigSchema = z.object({\n\tentry: z.string().optional(),\n\tsettingsSchema: z.record(z.string(), settingFieldSchema).optional(),\n\tpages: z.array(adminPageSchema).optional(),\n\twidgets: z.array(dashboardWidgetSchema).optional(),\n\tfieldWidgets: z\n\t\t.array(\n\t\t\tz.object({\n\t\t\t\tname: z.string().min(1),\n\t\t\t\tlabel: z.string().min(1),\n\t\t\t\tfieldTypes: z.array(z.enum(FIELD_TYPES)),\n\t\t\t\telements: z\n\t\t\t\t\t.array(\n\t\t\t\t\t\tz\n\t\t\t\t\t\t\t.object({\n\t\t\t\t\t\t\t\ttype: z.string(),\n\t\t\t\t\t\t\t\taction_id: z.string(),\n\t\t\t\t\t\t\t\tlabel: z.string().optional(),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.passthrough(),\n\t\t\t\t\t)\n\t\t\t\t\t.optional(),\n\t\t\t}),\n\t\t)\n\t\t.optional(),\n});\n\n// ── declaredAccess ──────────────────────────────────────────────\n\n/**\n * An operation's constraint object. Open vocabulary: keys the runtime\n * recognises are enforced, others are advisory. The bundler emits `{}` for a\n * granted operation; presence (not value) signals the grant.\n */\nconst accessConstraints = z.record(z.string(), z.unknown());\n\n/**\n * Structured trust contract embedded in the bundle manifest. Mirrors\n * `DeclaredAccess` in `@emdash-cms/plugin-types`. Categories are host\n * subsystems; operations are modes of participation.\n */\nconst declaredAccessSchema = z.object({\n\tcontent: z\n\t\t.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })\n\t\t.optional(),\n\tmedia: z\n\t\t.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })\n\t\t.optional(),\n\tnetwork: z\n\t\t.object({\n\t\t\t// allowedHosts: absent = unrestricted; present = host-restricted. Reject\n\t\t\t// an empty array (which the decoder would otherwise have to treat as\n\t\t\t// deny-all) to match the record lexicon's `minLength: 1` and keep the\n\t\t\t// \"absent vs empty\" distinction from ever reaching enforcement ambiguous.\n\t\t\trequest: z.object({ allowedHosts: z.array(z.string()).min(1).optional() }).optional(),\n\t\t})\n\t\t.optional(),\n\temail: z\n\t\t.object({\n\t\t\tsend: accessConstraints.optional(),\n\t\t\tevents: accessConstraints.optional(),\n\t\t\ttransport: accessConstraints.optional(),\n\t\t})\n\t\t.optional(),\n\tpage: z.object({ fragments: accessConstraints.optional() }).optional(),\n\tusers: z.object({ read: accessConstraints.optional() }).optional(),\n});\n\n// ── Main schema ─────────────────────────────────────────────────\n\n/**\n * Zod schema matching the PluginManifest interface from types.ts.\n *\n * Every JSON.parse of a manifest.json should validate through this.\n *\n * `declaredAccess` is the trust contract; `capabilities`/`allowedHosts` are the\n * runtime's enforcement currency. Apply `reconcileManifestAccess` after parsing\n * to make them consistent (declaredAccess authoritative when present). Kept a\n * plain object (no `.transform`) because callers `.pick()`/`.extend()` it.\n */\nexport const pluginManifestSchema = z.object({\n\tid: z.string().min(1),\n\tversion: z.string().min(1),\n\tdeclaredAccess: declaredAccessSchema.optional(),\n\tcapabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),\n\tallowedHosts: z.array(z.string()),\n\tstorage: z.record(z.string(), storageCollectionSchema),\n\t/**\n\t * Hook declarations — accepts both plain name strings (legacy) and\n\t * structured objects with exclusive/priority/timeout metadata.\n\t * Plain strings are normalized to `{ name }` objects after parsing.\n\t */\n\thooks: z.array(z.union([z.enum(HOOK_NAMES), manifestHookEntrySchema])),\n\t/**\n\t * Route declarations — accepts both plain name strings and\n\t * structured objects with public metadata.\n\t * Plain strings are normalized to `{ name }` objects after parsing.\n\t */\n\troutes: z.array(\n\t\tz.union([\n\t\t\tz.string().min(1).regex(routeNamePattern, \"Route name must be a safe path segment\"),\n\t\t\tmanifestRouteEntrySchema,\n\t\t]),\n\t),\n\tadmin: pluginAdminConfigSchema,\n});\n\nexport type ValidatedPluginManifest = z.infer<typeof pluginManifestSchema>;\n\n/**\n * Reconcile a parsed manifest's trust contract with its enforcement currency.\n * `declaredAccess` is authoritative: when present, `capabilities`/`allowedHosts`\n * are re-derived from it so what the runtime enforces always matches what was\n * recorded and consented to. A pre-migration bundle without `declaredAccess`\n * has it derived from the legacy capability list instead. The result always\n * carries both, mutually consistent. Apply this at every bundle-parse site.\n */\nexport function reconcileManifestAccess(manifest: ValidatedPluginManifest): PluginManifest {\n\tconst reconciled: ValidatedPluginManifest = manifest.declaredAccess\n\t\t? { ...manifest, ...declaredAccessToCapabilities(manifest.declaredAccess) }\n\t\t: {\n\t\t\t\t...manifest,\n\t\t\t\tdeclaredAccess: capabilitiesToDeclaredAccess(manifest.capabilities, manifest.allowedHosts),\n\t\t\t};\n\t// Block Kit admin elements are typed as `unknown` by the Zod schema (their\n\t// Element shape is validated at render time), so the validated manifest\n\t// needs a structural cast up to the runtime PluginManifest.\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- admin elements are unknown[] in Zod; Element type checked at render time\n\treturn reconciled as unknown as PluginManifest;\n}\n\n/**\n * Normalize a manifest hook entry — plain strings become `{ name }` objects.\n */\nexport function normalizeManifestHook(\n\tentry: string | { name: string; exclusive?: boolean; priority?: number; timeout?: number },\n): { name: string; exclusive?: boolean; priority?: number; timeout?: number } {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\treturn entry;\n}\n\n/**\n * Normalize a manifest route entry — plain strings become `{ name }` objects.\n */\nexport function normalizeManifestRoute(entry: string | { name: string; public?: boolean }): {\n\tname: string;\n\tpublic?: boolean;\n} {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\treturn entry;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"manifest-schema-DFPeqMAn.mjs","names":[],"sources":["../src/plugins/manifest-schema.ts"],"sourcesContent":["/**\n * Zod schema for PluginManifest validation\n *\n * Used to validate manifest.json from plugin bundles at every parse site:\n * - Client-side download (marketplace.ts extractBundle)\n * - R2 load (api/handlers/marketplace.ts loadBundleFromR2)\n * - CLI publish preview (cli/commands/publish.ts readManifestFromTarball)\n * - Marketplace ingest extends this with publishing-specific fields\n */\n\nimport {\n\tcapabilitiesToDeclaredAccess,\n\tdeclaredAccessToCapabilities,\n} from \"@emdash-cms/plugin-types\";\nimport { z } from \"zod\";\n\nimport type { PluginManifest } from \"./types.js\";\n\n// ── Enum values (must stay in sync with types.ts) ───────────────\n\n/**\n * Current capability names — the ones authors should use going forward.\n * See `PluginCapability` in `types.ts` for documentation of each.\n */\nexport const CURRENT_PLUGIN_CAPABILITIES = [\n\t\"network:request\",\n\t\"network:request:unrestricted\",\n\t\"content:read\",\n\t\"content:write\",\n\t\"media:read\",\n\t\"media:write\",\n\t\"users:read\",\n\t\"email:send\",\n\t\"hooks.email-transport:register\",\n\t\"hooks.email-events:register\",\n\t\"hooks.page-fragments:register\",\n] as const;\n\n/**\n * Legacy capability names accepted during the deprecation window.\n * Normalized to current names via `normalizeCapability()` in types.ts\n * before reaching the runtime. Plugin authors are warned at bundle/validate\n * and hard-failed at publish.\n */\nexport const DEPRECATED_PLUGIN_CAPABILITIES = [\n\t\"network:fetch\",\n\t\"network:fetch:any\",\n\t\"read:content\",\n\t\"write:content\",\n\t\"read:media\",\n\t\"write:media\",\n\t\"read:users\",\n\t\"email:provide\",\n\t\"email:intercept\",\n\t\"page:inject\",\n] as const;\n\n/**\n * Full set of accepted capability strings — current + deprecated.\n *\n * The manifest schema accepts both during the transition. The runtime only\n * ever sees current names because `normalizeCapability()` rewrites legacy\n * names at every external boundary (definePlugin, adaptSandboxEntry).\n */\nexport const PLUGIN_CAPABILITIES = [\n\t...CURRENT_PLUGIN_CAPABILITIES,\n\t...DEPRECATED_PLUGIN_CAPABILITIES,\n] as const;\n\n/** Must stay in sync with FieldType in schema/types.ts */\nconst FIELD_TYPES = [\n\t\"string\",\n\t\"text\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"multiSelect\",\n\t\"portableText\",\n\t\"image\",\n\t\"file\",\n\t\"reference\",\n\t\"json\",\n\t\"slug\",\n\t\"repeater\",\n] as const;\n\nexport const HOOK_NAMES = [\n\t\"plugin:install\",\n\t\"plugin:activate\",\n\t\"plugin:deactivate\",\n\t\"plugin:uninstall\",\n\t\"content:beforeSave\",\n\t\"content:afterSave\",\n\t\"content:beforeDelete\",\n\t\"content:afterDelete\",\n\t\"content:afterPublish\",\n\t\"content:afterUnpublish\",\n\t\"media:beforeUpload\",\n\t\"media:afterUpload\",\n\t\"cron\",\n\t\"email:beforeSend\",\n\t\"email:deliver\",\n\t\"email:afterSend\",\n\t\"comment:beforeCreate\",\n\t\"comment:moderate\",\n\t\"comment:afterCreate\",\n\t\"comment:afterModerate\",\n\t\"page:metadata\",\n\t\"page:fragments\",\n] as const;\n\n/**\n * Structured hook entry for manifest — name plus optional metadata.\n * During a transition period, both plain strings and objects are accepted.\n */\nconst manifestHookEntrySchema = z.object({\n\tname: z.enum(HOOK_NAMES),\n\texclusive: z.boolean().optional(),\n\tpriority: z.number().int().optional(),\n\ttimeout: z.number().int().positive().optional(),\n});\n\n/**\n * Structured route entry for manifest — name plus optional metadata.\n * Both plain strings and objects are accepted; strings are normalized\n * to `{ name }` objects via `normalizeManifestRoute()`.\n */\n/** Route names must be safe path segments — alphanumeric, hyphens, underscores, forward slashes */\nconst routeNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9_\\-/]*$/;\n\nconst manifestRouteEntrySchema = z.object({\n\tname: z.string().min(1).regex(routeNamePattern, \"Route name must be a safe path segment\"),\n\tpublic: z.boolean().optional(),\n});\n\n// ── Sub-schemas ─────────────────────────────────────────────────\n\n/** Index field names must be valid identifiers to prevent SQL injection via JSON path expressions */\nconst indexFieldName = z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]*$/);\n\nconst storageCollectionSchema = z.object({\n\tindexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])),\n\tuniqueIndexes: z.array(z.union([indexFieldName, z.array(indexFieldName)])).optional(),\n});\n\nconst baseSettingFields = {\n\tlabel: z.string(),\n\tdescription: z.string().optional(),\n};\n\nconst settingFieldSchema = z.discriminatedUnion(\"type\", [\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"string\"),\n\t\tdefault: z.string().optional(),\n\t\tmultiline: z.boolean().optional(),\n\t}),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"number\"),\n\t\tdefault: z.number().optional(),\n\t\tmin: z.number().optional(),\n\t\tmax: z.number().optional(),\n\t}),\n\tz.object({ ...baseSettingFields, type: z.literal(\"boolean\"), default: z.boolean().optional() }),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"select\"),\n\t\toptions: z.array(z.object({ value: z.string(), label: z.string() })),\n\t\tdefault: z.string().optional(),\n\t}),\n\tz.object({ ...baseSettingFields, type: z.literal(\"secret\") }),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"url\"),\n\t\tdefault: z.string().optional(),\n\t\tplaceholder: z.string().optional(),\n\t}),\n\tz.object({\n\t\t...baseSettingFields,\n\t\ttype: z.literal(\"email\"),\n\t\tdefault: z.string().optional(),\n\t\tplaceholder: z.string().optional(),\n\t}),\n]);\n\nconst adminPageSchema = z.object({\n\tpath: z.string(),\n\tlabel: z.string(),\n\ticon: z.string().optional(),\n});\n\nconst dashboardWidgetSchema = z.object({\n\tid: z.string(),\n\tsize: z.enum([\"full\", \"half\", \"third\"]).optional(),\n\ttitle: z.string().optional(),\n});\n\nconst pluginAdminConfigSchema = z.object({\n\tentry: z.string().optional(),\n\tsettingsSchema: z.record(z.string(), settingFieldSchema).optional(),\n\tpages: z.array(adminPageSchema).optional(),\n\twidgets: z.array(dashboardWidgetSchema).optional(),\n\tfieldWidgets: z\n\t\t.array(\n\t\t\tz.object({\n\t\t\t\tname: z.string().min(1),\n\t\t\t\tlabel: z.string().min(1),\n\t\t\t\tfieldTypes: z.array(z.enum(FIELD_TYPES)),\n\t\t\t\telements: z\n\t\t\t\t\t.array(\n\t\t\t\t\t\tz\n\t\t\t\t\t\t\t.object({\n\t\t\t\t\t\t\t\ttype: z.string(),\n\t\t\t\t\t\t\t\taction_id: z.string(),\n\t\t\t\t\t\t\t\tlabel: z.string().optional(),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.passthrough(),\n\t\t\t\t\t)\n\t\t\t\t\t.optional(),\n\t\t\t}),\n\t\t)\n\t\t.optional(),\n});\n\n// ── declaredAccess ──────────────────────────────────────────────\n\n/**\n * An operation's constraint object. Open vocabulary: keys the runtime\n * recognises are enforced, others are advisory. The bundler emits `{}` for a\n * granted operation; presence (not value) signals the grant.\n */\nconst accessConstraints = z.record(z.string(), z.unknown());\n\n/**\n * Structured trust contract embedded in the bundle manifest. Mirrors\n * `DeclaredAccess` in `@emdash-cms/plugin-types`. Categories are host\n * subsystems; operations are modes of participation.\n */\nconst declaredAccessSchema = z.object({\n\tcontent: z\n\t\t.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })\n\t\t.optional(),\n\tmedia: z\n\t\t.object({ read: accessConstraints.optional(), write: accessConstraints.optional() })\n\t\t.optional(),\n\tnetwork: z\n\t\t.object({\n\t\t\t// allowedHosts: absent = unrestricted; present = host-restricted. Reject\n\t\t\t// an empty array (which the decoder would otherwise have to treat as\n\t\t\t// deny-all) to match the record lexicon's `minLength: 1` and keep the\n\t\t\t// \"absent vs empty\" distinction from ever reaching enforcement ambiguous.\n\t\t\trequest: z.object({ allowedHosts: z.array(z.string()).min(1).optional() }).optional(),\n\t\t})\n\t\t.optional(),\n\temail: z\n\t\t.object({\n\t\t\tsend: accessConstraints.optional(),\n\t\t\tevents: accessConstraints.optional(),\n\t\t\ttransport: accessConstraints.optional(),\n\t\t})\n\t\t.optional(),\n\tpage: z.object({ fragments: accessConstraints.optional() }).optional(),\n\tusers: z.object({ read: accessConstraints.optional() }).optional(),\n});\n\n// ── Main schema ─────────────────────────────────────────────────\n\n/**\n * Zod schema matching the PluginManifest interface from types.ts.\n *\n * Every JSON.parse of a manifest.json should validate through this.\n *\n * `declaredAccess` is the trust contract; `capabilities`/`allowedHosts` are the\n * runtime's enforcement currency. Apply `reconcileManifestAccess` after parsing\n * to make them consistent (declaredAccess authoritative when present). Kept a\n * plain object (no `.transform`) because callers `.pick()`/`.extend()` it.\n */\nexport const pluginManifestSchema = z.object({\n\tid: z.string().min(1),\n\tversion: z.string().min(1),\n\tdeclaredAccess: declaredAccessSchema.optional(),\n\tcapabilities: z.array(z.enum(PLUGIN_CAPABILITIES)),\n\tallowedHosts: z.array(z.string()),\n\tstorage: z.record(z.string(), storageCollectionSchema),\n\t/**\n\t * Hook declarations — accepts both plain name strings (legacy) and\n\t * structured objects with exclusive/priority/timeout metadata.\n\t * Plain strings are normalized to `{ name }` objects after parsing.\n\t */\n\thooks: z.array(z.union([z.enum(HOOK_NAMES), manifestHookEntrySchema])),\n\t/**\n\t * Route declarations — accepts both plain name strings and\n\t * structured objects with public metadata.\n\t * Plain strings are normalized to `{ name }` objects after parsing.\n\t */\n\troutes: z.array(\n\t\tz.union([\n\t\t\tz.string().min(1).regex(routeNamePattern, \"Route name must be a safe path segment\"),\n\t\t\tmanifestRouteEntrySchema,\n\t\t]),\n\t),\n\tadmin: pluginAdminConfigSchema,\n});\n\nexport type ValidatedPluginManifest = z.infer<typeof pluginManifestSchema>;\n\n/**\n * Reconcile a parsed manifest's trust contract with its enforcement currency.\n * `declaredAccess` is authoritative: when present, `capabilities`/`allowedHosts`\n * are re-derived from it so what the runtime enforces always matches what was\n * recorded and consented to. A pre-migration bundle without `declaredAccess`\n * has it derived from the legacy capability list instead. The result always\n * carries both, mutually consistent. Apply this at every bundle-parse site.\n */\nexport function reconcileManifestAccess(manifest: ValidatedPluginManifest): PluginManifest {\n\tconst reconciled: ValidatedPluginManifest = manifest.declaredAccess\n\t\t? { ...manifest, ...declaredAccessToCapabilities(manifest.declaredAccess) }\n\t\t: {\n\t\t\t\t...manifest,\n\t\t\t\tdeclaredAccess: capabilitiesToDeclaredAccess(manifest.capabilities, manifest.allowedHosts),\n\t\t\t};\n\t// Block Kit admin elements are typed as `unknown` by the Zod schema (their\n\t// Element shape is validated at render time), so the validated manifest\n\t// needs a structural cast up to the runtime PluginManifest.\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- admin elements are unknown[] in Zod; Element type checked at render time\n\treturn reconciled as unknown as PluginManifest;\n}\n\n/**\n * Normalize a manifest hook entry — plain strings become `{ name }` objects.\n */\nexport function normalizeManifestHook(\n\tentry: string | { name: string; exclusive?: boolean; priority?: number; timeout?: number },\n): { name: string; exclusive?: boolean; priority?: number; timeout?: number } {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\treturn entry;\n}\n\n/**\n * Normalize a manifest route entry — plain strings become `{ name }` objects.\n */\nexport function normalizeManifestRoute(entry: string | { name: string; public?: boolean }): {\n\tname: string;\n\tpublic?: boolean;\n} {\n\tif (typeof entry === \"string\") {\n\t\treturn { name: entry };\n\t}\n\treturn entry;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { _ as MediaValue, a as ComponentEmbed, b as mediaItemToValue, c as EmbedResult, d as MediaListResult, f as MediaProvider, g as MediaUploadInput, h as MediaProviderItem, i as AudioEmbed, l as ImageEmbed, m as MediaProviderDescriptor, n as generatePlaceholder, o as CreateMediaProviderFn, p as MediaProviderCapabilities, r as normalizeMediaValue, s as EmbedOptions, t as PlaceholderData, u as MediaListOptions, v as ThumbnailOptions, y as VideoEmbed } from "../placeholder-CVBv5z8k.mjs";
1
+ import { _ as MediaValue, a as ComponentEmbed, b as mediaItemToValue, c as EmbedResult, d as MediaListResult, f as MediaProvider, g as MediaUploadInput, h as MediaProviderItem, i as AudioEmbed, l as ImageEmbed, m as MediaProviderDescriptor, n as generatePlaceholder, o as CreateMediaProviderFn, p as MediaProviderCapabilities, r as normalizeMediaValue, s as EmbedOptions, t as PlaceholderData, u as MediaListOptions, v as ThumbnailOptions, y as VideoEmbed } from "../placeholder-BevVKfay.mjs";
2
2
 
3
3
  //#region src/media/local.d.ts
4
4
  interface LocalMediaConfig {
@@ -1,5 +1,5 @@
1
- import { n as normalizeMediaValue } from "../normalize-CK5o04zr.mjs";
2
- import { t as generatePlaceholder } from "../placeholder-BZxr8W1j.mjs";
1
+ import { n as normalizeMediaValue } from "../normalize-DKsg36ty.mjs";
2
+ import { t as generatePlaceholder } from "../placeholder-2xumZh4g.mjs";
3
3
 
4
4
  //#region src/media/types.ts
5
5
  /**
@@ -1,14 +1,14 @@
1
- import "../options-DyYIYpPd.mjs";
2
- import { t as Database } from "../types-BPzXTV9x.mjs";
3
- import "../types-CPAPl93j.mjs";
4
- import "../byline-fields-DbibsvTl.mjs";
5
- import { h as MediaProviderItem, o as CreateMediaProviderFn } from "../placeholder-CVBv5z8k.mjs";
6
- import "../index-B1keaX5Y.mjs";
7
- import "../runner-DTdhuI9i.mjs";
8
- import "../index-DR56od45.mjs";
9
- import { d as Storage } from "../types-D4kUqbHh.mjs";
10
- import "../types-BFgrqwSk.mjs";
11
- import "../validate-CNwkPWzz.mjs";
1
+ import "../options-9kLgkE8m.mjs";
2
+ import { t as Database } from "../types-Del0VMij.mjs";
3
+ import "../types-DO7whVYU.mjs";
4
+ import "../byline-fields-CQJRIQkn.mjs";
5
+ import { h as MediaProviderItem, o as CreateMediaProviderFn } from "../placeholder-BevVKfay.mjs";
6
+ import "../index-D2VAiumu.mjs";
7
+ import "../runner-C8vcbvCe.mjs";
8
+ import "../index-uT2yR66F.mjs";
9
+ import { d as Storage } from "../types-u_XxjbS8.mjs";
10
+ import "../types-BFgYtuKd.mjs";
11
+ import "../validate-cJOiOvT2.mjs";
12
12
  import "../index.mjs";
13
13
  import { Kysely } from "kysely";
14
14
 
@@ -3,9 +3,10 @@ import "../base64-CqR-7kqF.mjs";
3
3
  import "../types-BXSUSAjt.mjs";
4
4
  import { t as MediaRepository } from "../media-JOf3pNkw.mjs";
5
5
  import "../options-BPCVnesz.mjs";
6
- import "../request-cache-D32LpnmI.mjs";
7
- import "../loader-ZN1ll-d-.mjs";
8
- import { o as invalidateSiteSettingsCache } from "../settings-DfxiWY_s.mjs";
6
+ import "../init-lock-DlBHjf9-.mjs";
7
+ import "../request-cache-UwmBAiUK.mjs";
8
+ import "../loader-BqWjcH3h.mjs";
9
+ import { o as invalidateSiteSettingsCache } from "../settings-BjBsmVAo.mjs";
9
10
 
10
11
  //#region src/media/local-runtime.ts
11
12
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"local-runtime.mjs","names":[],"sources":["../../src/media/local-runtime.ts"],"sourcesContent":["/**\n * Local Media Provider Runtime\n *\n * This is the runtime implementation loaded by the entrypoint.\n * It wraps the existing MediaRepository and storage adapter.\n *\n * Note: This provider is special because it needs access to the database\n * and storage adapter. The createMediaProvider function receives these\n * via the config object, injected by the runtime.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport type { Database } from \"../database/types.js\";\nimport type { Storage } from \"../index.js\";\nimport { invalidateSiteSettingsCache } from \"../settings/index.js\";\nimport type {\n\tCreateMediaProviderFn,\n\tMediaProvider,\n\tMediaListOptions,\n\tMediaProviderItem,\n\tMediaValue,\n\tEmbedResult,\n\tEmbedOptions,\n} from \"./types.js\";\n\nexport interface LocalMediaRuntimeConfig {\n\tenabled?: boolean;\n\t// These are injected by the runtime, not from user config\n\tdb?: Kysely<Database>;\n\tstorage?: Storage;\n}\n\n/**\n * Create the local media provider\n */\nexport const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig> = (config) => {\n\tconst { db, storage } = config;\n\n\tif (!db) {\n\t\tthrow new Error(\"Local media provider requires database connection\");\n\t}\n\n\tconst repo = new MediaRepository(db);\n\n\tconst provider: MediaProvider = {\n\t\tasync list(options: MediaListOptions) {\n\t\t\tconst result = await repo.findMany({\n\t\t\t\tcursor: options.cursor,\n\t\t\t\tlimit: options.limit,\n\t\t\t\tmimeType: options.mimeType,\n\t\t\t\t// TODO: Add search support when capabilities.search is true\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map((item) => ({\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tfilename: item.filename,\n\t\t\t\t\tmimeType: item.mimeType,\n\t\t\t\t\tsize: item.size ?? undefined,\n\t\t\t\t\twidth: item.width ?? undefined,\n\t\t\t\t\theight: item.height ?? undefined,\n\t\t\t\t\talt: item.alt ?? undefined,\n\t\t\t\t\tpreviewUrl: `/_emdash/api/media/file/${item.storageKey}`,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\tstorageKey: item.storageKey,\n\t\t\t\t\t\tcaption: item.caption,\n\t\t\t\t\t\tblurhash: item.blurhash,\n\t\t\t\t\t\tdominantColor: item.dominantColor,\n\t\t\t\t\t},\n\t\t\t\t})),\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t};\n\t\t},\n\n\t\tasync get(id: string) {\n\t\t\tconst item = await repo.findById(id);\n\t\t\tif (!item) return null;\n\n\t\t\treturn {\n\t\t\t\tid: item.id,\n\t\t\t\tfilename: item.filename,\n\t\t\t\tmimeType: item.mimeType,\n\t\t\t\tsize: item.size ?? undefined,\n\t\t\t\twidth: item.width ?? undefined,\n\t\t\t\theight: item.height ?? undefined,\n\t\t\t\talt: item.alt ?? undefined,\n\t\t\t\tpreviewUrl: `/_emdash/api/media/file/${item.storageKey}`,\n\t\t\t\tmeta: {\n\t\t\t\t\tstorageKey: item.storageKey,\n\t\t\t\t\tcaption: item.caption,\n\t\t\t\t\tblurhash: item.blurhash,\n\t\t\t\t\tdominantColor: item.dominantColor,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tasync upload(_input) {\n\t\t\tif (!storage) {\n\t\t\t\tthrow new Error(\"Storage not configured for local media provider\");\n\t\t\t}\n\n\t\t\t// This is handled by the existing media upload endpoint\n\t\t\t// The provider interface is used by external providers\n\t\t\t// For local, we delegate to the existing system\n\t\t\tthrow new Error(\"Local upload should use /_emdash/api/media endpoint\");\n\t\t},\n\n\t\tasync delete(id: string) {\n\t\t\tconst item = await repo.findById(id);\n\t\t\tif (!item) return;\n\n\t\t\t// Delete from storage if available\n\t\t\tif (storage) {\n\t\t\t\ttry {\n\t\t\t\t\tawait storage.delete(item.storageKey);\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore storage deletion errors\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait repo.delete(id);\n\n\t\t\t// If this row was referenced by `logo`, `favicon`, or\n\t\t\t// `seo.defaultOgImage`, the worker-scoped settings cache now\n\t\t\t// holds a stale URL. The provider routes (and any future caller)\n\t\t\t// bypass `handleMediaDelete`, so we invalidate here too.\n\t\t\tinvalidateSiteSettingsCache();\n\t\t},\n\n\t\tgetEmbed(value: MediaValue, _options?: EmbedOptions): EmbedResult {\n\t\t\tconst storageKey =\n\t\t\t\ttypeof value.meta?.storageKey === \"string\" ? value.meta.storageKey : value.id;\n\t\t\tconst src = `/_emdash/api/media/file/${storageKey}`;\n\t\t\tconst mimeType = value.mimeType || \"\";\n\n\t\t\t// Determine embed type based on MIME type\n\t\t\tif (mimeType.startsWith(\"image/\")) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tsrc,\n\t\t\t\t\twidth: value.width,\n\t\t\t\t\theight: value.height,\n\t\t\t\t\talt: value.alt,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (mimeType.startsWith(\"video/\")) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tsrc,\n\t\t\t\t\twidth: value.width,\n\t\t\t\t\theight: value.height,\n\t\t\t\t\tcontrols: true,\n\t\t\t\t\tpreload: \"metadata\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (mimeType.startsWith(\"audio/\")) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"audio\",\n\t\t\t\t\tsrc,\n\t\t\t\t\tcontrols: true,\n\t\t\t\t\tpreload: \"metadata\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Fallback: treat as image (for unknown types)\n\t\t\treturn {\n\t\t\t\ttype: \"image\",\n\t\t\t\tsrc,\n\t\t\t\twidth: value.width,\n\t\t\t\theight: value.height,\n\t\t\t\talt: value.alt,\n\t\t\t};\n\t\t},\n\n\t\tgetThumbnailUrl(id: string, _mimeType?: string) {\n\t\t\t// For local media, return the file URL\n\t\t\treturn `/_emdash/api/media/file/${id}`;\n\t\t},\n\t};\n\n\treturn provider;\n};\n\n/**\n * Helper to convert a MediaRepository item to MediaProviderItem\n */\nexport function repoItemToProviderItem(item: {\n\tid: string;\n\tfilename: string;\n\tmimeType: string;\n\tsize: number | null;\n\twidth: number | null;\n\theight: number | null;\n\talt: string | null;\n\tcaption: string | null;\n\tstorageKey: string;\n\tblurhash: string | null;\n\tdominantColor: string | null;\n}): MediaProviderItem {\n\treturn {\n\t\tid: item.id,\n\t\tfilename: item.filename,\n\t\tmimeType: item.mimeType,\n\t\tsize: item.size ?? undefined,\n\t\twidth: item.width ?? undefined,\n\t\theight: item.height ?? undefined,\n\t\talt: item.alt ?? undefined,\n\t\tpreviewUrl: `/_emdash/api/media/file/${item.storageKey}`,\n\t\tmeta: {\n\t\t\tstorageKey: item.storageKey,\n\t\t\tcaption: item.caption,\n\t\t\tblurhash: item.blurhash,\n\t\t\tdominantColor: item.dominantColor,\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;AAqCA,MAAa,uBAAuE,WAAW;CAC9F,MAAM,EAAE,IAAI,YAAY;AAExB,KAAI,CAAC,GACJ,OAAM,IAAI,MAAM,oDAAoD;CAGrE,MAAM,OAAO,IAAI,gBAAgB,GAAG;AA4IpC,QA1IgC;EAC/B,MAAM,KAAK,SAA2B;GACrC,MAAM,SAAS,MAAM,KAAK,SAAS;IAClC,QAAQ,QAAQ;IAChB,OAAO,QAAQ;IACf,UAAU,QAAQ;IAElB,CAAC;AAEF,UAAO;IACN,OAAO,OAAO,MAAM,KAAK,UAAU;KAClC,IAAI,KAAK;KACT,UAAU,KAAK;KACf,UAAU,KAAK;KACf,MAAM,KAAK,QAAQ;KACnB,OAAO,KAAK,SAAS;KACrB,QAAQ,KAAK,UAAU;KACvB,KAAK,KAAK,OAAO;KACjB,YAAY,2BAA2B,KAAK;KAC5C,MAAM;MACL,YAAY,KAAK;MACjB,SAAS,KAAK;MACd,UAAU,KAAK;MACf,eAAe,KAAK;MACpB;KACD,EAAE;IACH,YAAY,OAAO;IACnB;;EAGF,MAAM,IAAI,IAAY;GACrB,MAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,OAAI,CAAC,KAAM,QAAO;AAElB,UAAO;IACN,IAAI,KAAK;IACT,UAAU,KAAK;IACf,UAAU,KAAK;IACf,MAAM,KAAK,QAAQ;IACnB,OAAO,KAAK,SAAS;IACrB,QAAQ,KAAK,UAAU;IACvB,KAAK,KAAK,OAAO;IACjB,YAAY,2BAA2B,KAAK;IAC5C,MAAM;KACL,YAAY,KAAK;KACjB,SAAS,KAAK;KACd,UAAU,KAAK;KACf,eAAe,KAAK;KACpB;IACD;;EAGF,MAAM,OAAO,QAAQ;AACpB,OAAI,CAAC,QACJ,OAAM,IAAI,MAAM,kDAAkD;AAMnE,SAAM,IAAI,MAAM,sDAAsD;;EAGvE,MAAM,OAAO,IAAY;GACxB,MAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,OAAI,CAAC,KAAM;AAGX,OAAI,QACH,KAAI;AACH,UAAM,QAAQ,OAAO,KAAK,WAAW;WAC9B;AAKT,SAAM,KAAK,OAAO,GAAG;AAMrB,gCAA6B;;EAG9B,SAAS,OAAmB,UAAsC;GAGjE,MAAM,MAAM,2BADX,OAAO,MAAM,MAAM,eAAe,WAAW,MAAM,KAAK,aAAa,MAAM;GAE5E,MAAM,WAAW,MAAM,YAAY;AAGnC,OAAI,SAAS,WAAW,SAAS,CAChC,QAAO;IACN,MAAM;IACN;IACA,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,KAAK,MAAM;IACX;AAGF,OAAI,SAAS,WAAW,SAAS,CAChC,QAAO;IACN,MAAM;IACN;IACA,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,UAAU;IACV,SAAS;IACT;AAGF,OAAI,SAAS,WAAW,SAAS,CAChC,QAAO;IACN,MAAM;IACN;IACA,UAAU;IACV,SAAS;IACT;AAIF,UAAO;IACN,MAAM;IACN;IACA,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,KAAK,MAAM;IACX;;EAGF,gBAAgB,IAAY,WAAoB;AAE/C,UAAO,2BAA2B;;EAEnC;;;;;AAQF,SAAgB,uBAAuB,MAYjB;AACrB,QAAO;EACN,IAAI,KAAK;EACT,UAAU,KAAK;EACf,UAAU,KAAK;EACf,MAAM,KAAK,QAAQ;EACnB,OAAO,KAAK,SAAS;EACrB,QAAQ,KAAK,UAAU;EACvB,KAAK,KAAK,OAAO;EACjB,YAAY,2BAA2B,KAAK;EAC5C,MAAM;GACL,YAAY,KAAK;GACjB,SAAS,KAAK;GACd,UAAU,KAAK;GACf,eAAe,KAAK;GACpB;EACD"}
1
+ {"version":3,"file":"local-runtime.mjs","names":[],"sources":["../../src/media/local-runtime.ts"],"sourcesContent":["/**\n * Local Media Provider Runtime\n *\n * This is the runtime implementation loaded by the entrypoint.\n * It wraps the existing MediaRepository and storage adapter.\n *\n * Note: This provider is special because it needs access to the database\n * and storage adapter. The createMediaProvider function receives these\n * via the config object, injected by the runtime.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport type { Database } from \"../database/types.js\";\nimport type { Storage } from \"../index.js\";\nimport { invalidateSiteSettingsCache } from \"../settings/index.js\";\nimport type {\n\tCreateMediaProviderFn,\n\tMediaProvider,\n\tMediaListOptions,\n\tMediaProviderItem,\n\tMediaValue,\n\tEmbedResult,\n\tEmbedOptions,\n} from \"./types.js\";\n\nexport interface LocalMediaRuntimeConfig {\n\tenabled?: boolean;\n\t// These are injected by the runtime, not from user config\n\tdb?: Kysely<Database>;\n\tstorage?: Storage;\n}\n\n/**\n * Create the local media provider\n */\nexport const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig> = (config) => {\n\tconst { db, storage } = config;\n\n\tif (!db) {\n\t\tthrow new Error(\"Local media provider requires database connection\");\n\t}\n\n\tconst repo = new MediaRepository(db);\n\n\tconst provider: MediaProvider = {\n\t\tasync list(options: MediaListOptions) {\n\t\t\tconst result = await repo.findMany({\n\t\t\t\tcursor: options.cursor,\n\t\t\t\tlimit: options.limit,\n\t\t\t\tmimeType: options.mimeType,\n\t\t\t\t// TODO: Add search support when capabilities.search is true\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map((item) => ({\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tfilename: item.filename,\n\t\t\t\t\tmimeType: item.mimeType,\n\t\t\t\t\tsize: item.size ?? undefined,\n\t\t\t\t\twidth: item.width ?? undefined,\n\t\t\t\t\theight: item.height ?? undefined,\n\t\t\t\t\talt: item.alt ?? undefined,\n\t\t\t\t\tpreviewUrl: `/_emdash/api/media/file/${item.storageKey}`,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\tstorageKey: item.storageKey,\n\t\t\t\t\t\tcaption: item.caption,\n\t\t\t\t\t\tblurhash: item.blurhash,\n\t\t\t\t\t\tdominantColor: item.dominantColor,\n\t\t\t\t\t},\n\t\t\t\t})),\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t};\n\t\t},\n\n\t\tasync get(id: string) {\n\t\t\tconst item = await repo.findById(id);\n\t\t\tif (!item) return null;\n\n\t\t\treturn {\n\t\t\t\tid: item.id,\n\t\t\t\tfilename: item.filename,\n\t\t\t\tmimeType: item.mimeType,\n\t\t\t\tsize: item.size ?? undefined,\n\t\t\t\twidth: item.width ?? undefined,\n\t\t\t\theight: item.height ?? undefined,\n\t\t\t\talt: item.alt ?? undefined,\n\t\t\t\tpreviewUrl: `/_emdash/api/media/file/${item.storageKey}`,\n\t\t\t\tmeta: {\n\t\t\t\t\tstorageKey: item.storageKey,\n\t\t\t\t\tcaption: item.caption,\n\t\t\t\t\tblurhash: item.blurhash,\n\t\t\t\t\tdominantColor: item.dominantColor,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\tasync upload(_input) {\n\t\t\tif (!storage) {\n\t\t\t\tthrow new Error(\"Storage not configured for local media provider\");\n\t\t\t}\n\n\t\t\t// This is handled by the existing media upload endpoint\n\t\t\t// The provider interface is used by external providers\n\t\t\t// For local, we delegate to the existing system\n\t\t\tthrow new Error(\"Local upload should use /_emdash/api/media endpoint\");\n\t\t},\n\n\t\tasync delete(id: string) {\n\t\t\tconst item = await repo.findById(id);\n\t\t\tif (!item) return;\n\n\t\t\t// Delete from storage if available\n\t\t\tif (storage) {\n\t\t\t\ttry {\n\t\t\t\t\tawait storage.delete(item.storageKey);\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore storage deletion errors\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait repo.delete(id);\n\n\t\t\t// If this row was referenced by `logo`, `favicon`, or\n\t\t\t// `seo.defaultOgImage`, the worker-scoped settings cache now\n\t\t\t// holds a stale URL. The provider routes (and any future caller)\n\t\t\t// bypass `handleMediaDelete`, so we invalidate here too.\n\t\t\tinvalidateSiteSettingsCache();\n\t\t},\n\n\t\tgetEmbed(value: MediaValue, _options?: EmbedOptions): EmbedResult {\n\t\t\tconst storageKey =\n\t\t\t\ttypeof value.meta?.storageKey === \"string\" ? value.meta.storageKey : value.id;\n\t\t\tconst src = `/_emdash/api/media/file/${storageKey}`;\n\t\t\tconst mimeType = value.mimeType || \"\";\n\n\t\t\t// Determine embed type based on MIME type\n\t\t\tif (mimeType.startsWith(\"image/\")) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tsrc,\n\t\t\t\t\twidth: value.width,\n\t\t\t\t\theight: value.height,\n\t\t\t\t\talt: value.alt,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (mimeType.startsWith(\"video/\")) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tsrc,\n\t\t\t\t\twidth: value.width,\n\t\t\t\t\theight: value.height,\n\t\t\t\t\tcontrols: true,\n\t\t\t\t\tpreload: \"metadata\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (mimeType.startsWith(\"audio/\")) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: \"audio\",\n\t\t\t\t\tsrc,\n\t\t\t\t\tcontrols: true,\n\t\t\t\t\tpreload: \"metadata\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Fallback: treat as image (for unknown types)\n\t\t\treturn {\n\t\t\t\ttype: \"image\",\n\t\t\t\tsrc,\n\t\t\t\twidth: value.width,\n\t\t\t\theight: value.height,\n\t\t\t\talt: value.alt,\n\t\t\t};\n\t\t},\n\n\t\tgetThumbnailUrl(id: string, _mimeType?: string) {\n\t\t\t// For local media, return the file URL\n\t\t\treturn `/_emdash/api/media/file/${id}`;\n\t\t},\n\t};\n\n\treturn provider;\n};\n\n/**\n * Helper to convert a MediaRepository item to MediaProviderItem\n */\nexport function repoItemToProviderItem(item: {\n\tid: string;\n\tfilename: string;\n\tmimeType: string;\n\tsize: number | null;\n\twidth: number | null;\n\theight: number | null;\n\talt: string | null;\n\tcaption: string | null;\n\tstorageKey: string;\n\tblurhash: string | null;\n\tdominantColor: string | null;\n}): MediaProviderItem {\n\treturn {\n\t\tid: item.id,\n\t\tfilename: item.filename,\n\t\tmimeType: item.mimeType,\n\t\tsize: item.size ?? undefined,\n\t\twidth: item.width ?? undefined,\n\t\theight: item.height ?? undefined,\n\t\talt: item.alt ?? undefined,\n\t\tpreviewUrl: `/_emdash/api/media/file/${item.storageKey}`,\n\t\tmeta: {\n\t\t\tstorageKey: item.storageKey,\n\t\t\tcaption: item.caption,\n\t\t\tblurhash: item.blurhash,\n\t\t\tdominantColor: item.dominantColor,\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AAqCA,MAAa,uBAAuE,WAAW;CAC9F,MAAM,EAAE,IAAI,YAAY;AAExB,KAAI,CAAC,GACJ,OAAM,IAAI,MAAM,oDAAoD;CAGrE,MAAM,OAAO,IAAI,gBAAgB,GAAG;AA4IpC,QA1IgC;EAC/B,MAAM,KAAK,SAA2B;GACrC,MAAM,SAAS,MAAM,KAAK,SAAS;IAClC,QAAQ,QAAQ;IAChB,OAAO,QAAQ;IACf,UAAU,QAAQ;IAElB,CAAC;AAEF,UAAO;IACN,OAAO,OAAO,MAAM,KAAK,UAAU;KAClC,IAAI,KAAK;KACT,UAAU,KAAK;KACf,UAAU,KAAK;KACf,MAAM,KAAK,QAAQ;KACnB,OAAO,KAAK,SAAS;KACrB,QAAQ,KAAK,UAAU;KACvB,KAAK,KAAK,OAAO;KACjB,YAAY,2BAA2B,KAAK;KAC5C,MAAM;MACL,YAAY,KAAK;MACjB,SAAS,KAAK;MACd,UAAU,KAAK;MACf,eAAe,KAAK;MACpB;KACD,EAAE;IACH,YAAY,OAAO;IACnB;;EAGF,MAAM,IAAI,IAAY;GACrB,MAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,OAAI,CAAC,KAAM,QAAO;AAElB,UAAO;IACN,IAAI,KAAK;IACT,UAAU,KAAK;IACf,UAAU,KAAK;IACf,MAAM,KAAK,QAAQ;IACnB,OAAO,KAAK,SAAS;IACrB,QAAQ,KAAK,UAAU;IACvB,KAAK,KAAK,OAAO;IACjB,YAAY,2BAA2B,KAAK;IAC5C,MAAM;KACL,YAAY,KAAK;KACjB,SAAS,KAAK;KACd,UAAU,KAAK;KACf,eAAe,KAAK;KACpB;IACD;;EAGF,MAAM,OAAO,QAAQ;AACpB,OAAI,CAAC,QACJ,OAAM,IAAI,MAAM,kDAAkD;AAMnE,SAAM,IAAI,MAAM,sDAAsD;;EAGvE,MAAM,OAAO,IAAY;GACxB,MAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,OAAI,CAAC,KAAM;AAGX,OAAI,QACH,KAAI;AACH,UAAM,QAAQ,OAAO,KAAK,WAAW;WAC9B;AAKT,SAAM,KAAK,OAAO,GAAG;AAMrB,gCAA6B;;EAG9B,SAAS,OAAmB,UAAsC;GAGjE,MAAM,MAAM,2BADX,OAAO,MAAM,MAAM,eAAe,WAAW,MAAM,KAAK,aAAa,MAAM;GAE5E,MAAM,WAAW,MAAM,YAAY;AAGnC,OAAI,SAAS,WAAW,SAAS,CAChC,QAAO;IACN,MAAM;IACN;IACA,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,KAAK,MAAM;IACX;AAGF,OAAI,SAAS,WAAW,SAAS,CAChC,QAAO;IACN,MAAM;IACN;IACA,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,UAAU;IACV,SAAS;IACT;AAGF,OAAI,SAAS,WAAW,SAAS,CAChC,QAAO;IACN,MAAM;IACN;IACA,UAAU;IACV,SAAS;IACT;AAIF,UAAO;IACN,MAAM;IACN;IACA,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,KAAK,MAAM;IACX;;EAGF,gBAAgB,IAAY,WAAoB;AAE/C,UAAO,2BAA2B;;EAEnC;;;;;AAQF,SAAgB,uBAAuB,MAYjB;AACrB,QAAO;EACN,IAAI,KAAK;EACT,UAAU,KAAK;EACf,UAAU,KAAK;EACf,MAAM,KAAK,QAAQ;EACnB,OAAO,KAAK,SAAS;EACrB,QAAQ,KAAK,UAAU;EACvB,KAAK,KAAK,OAAO;EACjB,YAAY,2BAA2B,KAAK;EAC5C,MAAM;GACL,YAAY,KAAK;GACjB,SAAS,KAAK;GACd,UAAU,KAAK;GACf,eAAe,KAAK;GACpB;EACD"}
@@ -1,4 +1,4 @@
1
- import { r as parseAllowedMimeTypes } from "./mime-CCEzze7W.mjs";
1
+ import { r as parseAllowedMimeTypes } from "./mime-YbtlEtvS.mjs";
2
2
 
3
3
  //#region src/api/handlers/media-allowlist.ts
4
4
  /**
@@ -29,4 +29,4 @@ async function resolveFieldAllowlist(db, fieldId) {
29
29
 
30
30
  //#endregion
31
31
  export { resolveFieldAllowlist as n, GLOBAL_UPLOAD_ALLOWLIST as t };
32
- //# sourceMappingURL=media-allowlist-Dknq-OFY.mjs.map
32
+ //# sourceMappingURL=media-allowlist-_A0SuDn4.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"media-allowlist-Dknq-OFY.mjs","names":[],"sources":["../src/api/handlers/media-allowlist.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { parseAllowedMimeTypes } from \"../../media/mime.js\";\n\n/**\n * MIME types allowed for upload by default (when no field-specific list\n * overrides this). Entries ending with \"/\" are prefix-matched (e.g.\n * \"image/\" matches \"image/jpeg\", \"image/png\", etc.).\n */\nexport const GLOBAL_UPLOAD_ALLOWLIST: readonly string[] = [\n\t\"image/\",\n\t\"video/\",\n\t\"audio/\",\n\t\"application/pdf\",\n];\n\n/**\n * Resolve the MIME allowlist for a specific field.\n *\n * Returns the field's `allowedMimeTypes` list when the field exists, is of\n * type \"file\" or \"image\", and has a non-empty list configured. Returns null\n * in all other cases — callers should fall back to GLOBAL_UPLOAD_ALLOWLIST.\n *\n * Authentication is the caller's responsibility (the upload routes already\n * gate on `media:upload`).\n */\nexport async function resolveFieldAllowlist(\n\tdb: Kysely<Database>,\n\tfieldId: string,\n): Promise<string[] | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.select([\"type\", \"validation\"])\n\t\t.where(\"id\", \"=\", fieldId)\n\t\t.where(\"type\", \"in\", [\"file\", \"image\"])\n\t\t.executeTakeFirst();\n\n\treturn row ? parseAllowedMimeTypes(row.validation) : null;\n}\n"],"mappings":";;;;;;;;AAUA,MAAa,0BAA6C;CACzD;CACA;CACA;CACA;CACA;;;;;;;;;;;AAYD,eAAsB,sBACrB,IACA,SAC2B;CAC3B,MAAM,MAAM,MAAM,GAChB,WAAW,iBAAiB,CAC5B,OAAO,CAAC,QAAQ,aAAa,CAAC,CAC9B,MAAM,MAAM,KAAK,QAAQ,CACzB,MAAM,QAAQ,MAAM,CAAC,QAAQ,QAAQ,CAAC,CACtC,kBAAkB;AAEpB,QAAO,MAAM,sBAAsB,IAAI,WAAW,GAAG"}
1
+ {"version":3,"file":"media-allowlist-_A0SuDn4.mjs","names":[],"sources":["../src/api/handlers/media-allowlist.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { parseAllowedMimeTypes } from \"../../media/mime.js\";\n\n/**\n * MIME types allowed for upload by default (when no field-specific list\n * overrides this). Entries ending with \"/\" are prefix-matched (e.g.\n * \"image/\" matches \"image/jpeg\", \"image/png\", etc.).\n */\nexport const GLOBAL_UPLOAD_ALLOWLIST: readonly string[] = [\n\t\"image/\",\n\t\"video/\",\n\t\"audio/\",\n\t\"application/pdf\",\n];\n\n/**\n * Resolve the MIME allowlist for a specific field.\n *\n * Returns the field's `allowedMimeTypes` list when the field exists, is of\n * type \"file\" or \"image\", and has a non-empty list configured. Returns null\n * in all other cases — callers should fall back to GLOBAL_UPLOAD_ALLOWLIST.\n *\n * Authentication is the caller's responsibility (the upload routes already\n * gate on `media:upload`).\n */\nexport async function resolveFieldAllowlist(\n\tdb: Kysely<Database>,\n\tfieldId: string,\n): Promise<string[] | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.select([\"type\", \"validation\"])\n\t\t.where(\"id\", \"=\", fieldId)\n\t\t.where(\"type\", \"in\", [\"file\", \"image\"])\n\t\t.executeTakeFirst();\n\n\treturn row ? parseAllowedMimeTypes(row.validation) : null;\n}\n"],"mappings":";;;;;;;;AAUA,MAAa,0BAA6C;CACzD;CACA;CACA;CACA;CACA;;;;;;;;;;;AAYD,eAAsB,sBACrB,IACA,SAC2B;CAC3B,MAAM,MAAM,MAAM,GAChB,WAAW,iBAAiB,CAC5B,OAAO,CAAC,QAAQ,aAAa,CAAC,CAC9B,MAAM,MAAM,KAAK,QAAQ,CACzB,MAAM,QAAQ,MAAM,CAAC,QAAQ,QAAQ,CAAC,CACtC,kBAAkB;AAEpB,QAAO,MAAM,sBAAsB,IAAI,WAAW,GAAG"}
@@ -23,4 +23,4 @@ function buildSeoImageUrl(imageRef, siteUrl) {
23
23
 
24
24
  //#endregion
25
25
  export { buildSeoImageUrl as t };
26
- //# sourceMappingURL=media-url-VClf8glU.mjs.map
26
+ //# sourceMappingURL=media-url-CqLd69IO.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"media-url-VClf8glU.mjs","names":[],"sources":["../src/seo/media-url.ts"],"sourcesContent":["/**\n * Resolve a stored SEO image reference to a URL.\n *\n * The CMS SEO panel stores `seo_image` in one of these shapes:\n * - an absolute URL (`https://...`) — returned as-is;\n * - a root-relative path that already includes the media API prefix\n * (`/_emdash/api/media/file/01KS....webp`) — prefixed with `siteUrl`;\n * - a bare media id (`01KS...`) — expanded to the media API path, then\n * prefixed with `siteUrl`.\n *\n * Shared by the SEO meta builder (`og:image`) and the sitemap route\n * (`<image:image>`) so both resolve image references identically.\n */\nconst TRAILING_SLASH_RE = /\\/$/;\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//i;\n\nexport function buildSeoImageUrl(imageRef: string, siteUrl?: string): string {\n\t// Already absolute — use as-is.\n\tif (ABSOLUTE_URL_RE.test(imageRef)) {\n\t\treturn imageRef;\n\t}\n\n\t// Root-relative path (already includes the media API prefix). Without\n\t// this branch we'd re-prefix and produce a doubled path that 404s.\n\tif (imageRef.startsWith(\"/\")) {\n\t\treturn siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${imageRef}` : imageRef;\n\t}\n\n\t// Bare media id — build the full media API path.\n\tconst mediaPath = `/_emdash/api/media/file/${imageRef}`;\n\treturn siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${mediaPath}` : mediaPath;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AAExB,SAAgB,iBAAiB,UAAkB,SAA0B;AAE5E,KAAI,gBAAgB,KAAK,SAAS,CACjC,QAAO;AAKR,KAAI,SAAS,WAAW,IAAI,CAC3B,QAAO,UAAU,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG,aAAa;CAI3E,MAAM,YAAY,2BAA2B;AAC7C,QAAO,UAAU,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG,cAAc"}
1
+ {"version":3,"file":"media-url-CqLd69IO.mjs","names":[],"sources":["../src/seo/media-url.ts"],"sourcesContent":["/**\n * Resolve a stored SEO image reference to a URL.\n *\n * The CMS SEO panel stores `seo_image` in one of these shapes:\n * - an absolute URL (`https://...`) — returned as-is;\n * - a root-relative path that already includes the media API prefix\n * (`/_emdash/api/media/file/01KS....webp`) — prefixed with `siteUrl`;\n * - a bare media id (`01KS...`) — expanded to the media API path, then\n * prefixed with `siteUrl`.\n *\n * Shared by the SEO meta builder (`og:image`) and the sitemap route\n * (`<image:image>`) so both resolve image references identically.\n */\nconst TRAILING_SLASH_RE = /\\/$/;\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//i;\n\nexport function buildSeoImageUrl(imageRef: string, siteUrl?: string): string {\n\t// Already absolute — use as-is.\n\tif (ABSOLUTE_URL_RE.test(imageRef)) {\n\t\treturn imageRef;\n\t}\n\n\t// Root-relative path (already includes the media API prefix). Without\n\t// this branch we'd re-prefix and produce a doubled path that 404s.\n\tif (imageRef.startsWith(\"/\")) {\n\t\treturn siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${imageRef}` : imageRef;\n\t}\n\n\t// Bare media id — build the full media API path.\n\tconst mediaPath = `/_emdash/api/media/file/${imageRef}`;\n\treturn siteUrl ? `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${mediaPath}` : mediaPath;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AAExB,SAAgB,iBAAiB,UAAkB,SAA0B;AAE5E,KAAI,gBAAgB,KAAK,SAAS,CACjC,QAAO;AAKR,KAAI,SAAS,WAAW,IAAI,CAC3B,QAAO,UAAU,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG,aAAa;CAI3E,MAAM,YAAY,2BAA2B;AAC7C,QAAO,UAAU,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG,cAAc"}
@@ -1,14 +1,14 @@
1
1
  import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
2
2
  import { t as CommentRepository } from "./comment-sqQxNpN3.mjs";
3
3
  import { t as OptionsRepository } from "./options-BPCVnesz.mjs";
4
- import { t as PluginContextFactory } from "./context-Y7BRkWes.mjs";
5
- import { r as requestCached } from "./request-cache-D32LpnmI.mjs";
6
- import { r as getDb } from "./loader-ZN1ll-d-.mjs";
7
- import { i as resolveLocaleChain, r as resolveLocale } from "./resolve-BqYMVG0D.mjs";
8
- import { a as normalizeCapabilities } from "./types-CZI4E3qG.mjs";
9
- import { dt as sanitizeHref } from "./redirects-CCbCqCCd.mjs";
10
- import { t as extractRequestMeta } from "./request-meta-7ByVLxB-.mjs";
11
- import { r as setCronTasksEnabled } from "./cron-BJ2ClIlj.mjs";
4
+ import { t as PluginContextFactory } from "./context-Cm4pt1Ws.mjs";
5
+ import { r as requestCached } from "./request-cache-UwmBAiUK.mjs";
6
+ import { r as getDb } from "./loader-BqWjcH3h.mjs";
7
+ import { i as resolveLocaleChain, r as resolveLocale } from "./resolve-B3NUUtVY.mjs";
8
+ import { a as normalizeCapabilities } from "./types-BoRm8-pp.mjs";
9
+ import { dt as sanitizeHref } from "./redirects-6Zg2SoYo.mjs";
10
+ import { t as extractRequestMeta } from "./request-meta-DPechd0W.mjs";
11
+ import { r as setCronTasksEnabled } from "./cron-DdEVrQ2Y.mjs";
12
12
  import { sql } from "kysely";
13
13
  import { ulid } from "ulidx";
14
14
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -2895,4 +2895,4 @@ async function resolveTaxonomyUrl(referenceGroup, db, locale) {
2895
2895
 
2896
2896
  //#endregion
2897
2897
  export { file as C, reference as S, resolveExclusiveHooks as _, SandboxUnavailableError as a, prosemirrorToPortableText as b, createNoopSandboxRunner as c, createPluginManager as d, PluginRouteError as f, createHookPipeline as g, HookPipeline as h, getComments as i, NodeCronScheduler as l, EmailPipeline as m, getMenus as n, NoopSandboxRunner as o, PluginRouteRegistry as p, getCommentCount as r, SandboxNotAvailableError as s, getMenu as t, PluginManager as u, definePlugin as v, image as w, portableText as x, portableTextToProsemirror as y };
2898
- //# sourceMappingURL=menus-DrQLusqj.mjs.map
2898
+ //# sourceMappingURL=menus-Ryk9L7fT.mjs.map