emdash 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/dist/{adapters-C5AWLJSD.d.mts → adapters-BzIHV3sw.d.mts} +1 -1
  2. package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BzIHV3sw.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-B1u7Qnvg.mjs} +2 -2
  4. package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-B1u7Qnvg.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +5 -5
  7. package/dist/api/schemas/index.d.mts +1 -1
  8. package/dist/api/schemas/index.mjs +2 -2
  9. package/dist/{api-BZ6bhjYs.mjs → api-DStv36ik.mjs} +36 -5
  10. package/dist/api-DStv36ik.mjs.map +1 -0
  11. package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-DPfhPu5V.mjs} +2 -2
  12. package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-DPfhPu5V.mjs.map} +1 -1
  13. package/dist/{apply-hQkKKBCf.mjs → apply-Dr7snAMT.mjs} +7 -7
  14. package/dist/{apply-hQkKKBCf.mjs.map → apply-Dr7snAMT.mjs.map} +1 -1
  15. package/dist/astro/index.d.mts +10 -10
  16. package/dist/astro/index.mjs +3 -3
  17. package/dist/astro/middleware/auth.d.mts +9 -9
  18. package/dist/astro/middleware/auth.mjs +4 -4
  19. package/dist/astro/middleware/redirect.mjs +1 -1
  20. package/dist/astro/middleware/request-context.mjs +1 -1
  21. package/dist/astro/middleware/setup.mjs +1 -1
  22. package/dist/astro/middleware.d.mts +1 -1
  23. package/dist/astro/middleware.d.mts.map +1 -1
  24. package/dist/astro/middleware.mjs +63 -112
  25. package/dist/astro/middleware.mjs.map +1 -1
  26. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +2 -2
  27. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +2 -2
  28. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  29. package/dist/astro/routes/api/admin/api-tokens/index.mjs +2 -2
  30. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +2 -2
  31. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
  32. package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
  33. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
  34. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +6 -6
  35. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +6 -6
  36. package/dist/astro/routes/api/admin/bylines/index.mjs +6 -6
  37. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +6 -6
  38. package/dist/astro/routes/api/admin/comments/_id_.mjs +2 -2
  39. package/dist/astro/routes/api/admin/comments/bulk.mjs +4 -4
  40. package/dist/astro/routes/api/admin/comments/counts.mjs +2 -2
  41. package/dist/astro/routes/api/admin/comments/index.mjs +4 -4
  42. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +1 -1
  43. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +1 -1
  44. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +1 -1
  45. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +1 -1
  46. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +14 -14
  47. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +14 -14
  48. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +14 -14
  49. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +14 -14
  50. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +14 -14
  51. package/dist/astro/routes/api/admin/plugins/index.mjs +14 -14
  52. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +1 -1
  53. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +14 -14
  54. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +14 -14
  55. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +14 -14
  56. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +14 -14
  57. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +15 -15
  58. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +14 -14
  59. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +15 -15
  60. package/dist/astro/routes/api/admin/plugins/updates.mjs +14 -14
  61. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +14 -14
  62. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +1 -1
  63. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +14 -14
  64. package/dist/astro/routes/api/admin/users/_id_/index.mjs +2 -2
  65. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +1 -1
  66. package/dist/astro/routes/api/admin/users/index.mjs +2 -2
  67. package/dist/astro/routes/api/auth/dev-bypass.mjs +2 -2
  68. package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
  69. package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
  70. package/dist/astro/routes/api/auth/invite/register-options.mjs +5 -5
  71. package/dist/astro/routes/api/auth/logout.mjs +1 -1
  72. package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
  73. package/dist/astro/routes/api/auth/magic-link/verify.mjs +1 -1
  74. package/dist/astro/routes/api/auth/me.mjs +2 -2
  75. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  76. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  77. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  78. package/dist/astro/routes/api/auth/passkey/_id_.mjs +2 -2
  79. package/dist/astro/routes/api/auth/passkey/options.mjs +6 -6
  80. package/dist/astro/routes/api/auth/passkey/register/options.mjs +5 -5
  81. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
  82. package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
  83. package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
  84. package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
  85. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +6 -6
  86. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +1 -1
  87. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +1 -1
  88. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +1 -1
  89. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +1 -1
  90. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
  91. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +3 -3
  92. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +1 -1
  93. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +1 -1
  94. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +5 -5
  96. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +1 -1
  97. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +1 -1
  98. package/dist/astro/routes/api/content/_collection_/_id_.mjs +3 -3
  99. package/dist/astro/routes/api/content/_collection_/authors.mjs +1 -1
  100. package/dist/astro/routes/api/content/_collection_/index.mjs +3 -3
  101. package/dist/astro/routes/api/content/_collection_/trash.mjs +3 -3
  102. package/dist/astro/routes/api/dashboard.mjs +1 -1
  103. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  104. package/dist/astro/routes/api/import/probe.mjs +3 -3
  105. package/dist/astro/routes/api/import/wordpress/analyze.mjs +1 -1
  106. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  107. package/dist/astro/routes/api/import/wordpress/execute.mjs +3 -3
  108. package/dist/astro/routes/api/import/wordpress/media.mjs +3 -3
  109. package/dist/astro/routes/api/import/wordpress/prepare.mjs +3 -3
  110. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +3 -3
  111. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  112. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +3 -3
  113. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  114. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +3 -3
  115. package/dist/astro/routes/api/manifest.mjs +2 -2
  116. package/dist/astro/routes/api/mcp.mjs +18 -18
  117. package/dist/astro/routes/api/media/_id_/confirm.mjs +3 -3
  118. package/dist/astro/routes/api/media/_id_.mjs +3 -3
  119. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +1 -1
  120. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +1 -1
  121. package/dist/astro/routes/api/media/providers/index.mjs +1 -1
  122. package/dist/astro/routes/api/media/upload-url.mjs +4 -4
  123. package/dist/astro/routes/api/media.mjs +4 -4
  124. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +3 -3
  125. package/dist/astro/routes/api/menus/_name_/items.mjs +3 -3
  126. package/dist/astro/routes/api/menus/_name_/reorder.mjs +3 -3
  127. package/dist/astro/routes/api/menus/_name_/translations.mjs +3 -3
  128. package/dist/astro/routes/api/menus/_name_.mjs +3 -3
  129. package/dist/astro/routes/api/menus/index.mjs +3 -3
  130. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  131. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  132. package/dist/astro/routes/api/oauth/device/code.mjs +5 -5
  133. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  134. package/dist/astro/routes/api/oauth/register.mjs +1 -1
  135. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  136. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  137. package/dist/astro/routes/api/oauth/token.mjs +4 -4
  138. package/dist/astro/routes/api/openapi.json.mjs +1 -1
  139. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +2 -2
  140. package/dist/astro/routes/api/redirects/404s/index.mjs +4 -4
  141. package/dist/astro/routes/api/redirects/404s/summary.mjs +4 -4
  142. package/dist/astro/routes/api/redirects/_id_.mjs +4 -4
  143. package/dist/astro/routes/api/redirects/index.mjs +4 -4
  144. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +1 -1
  145. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +1 -1
  146. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +14 -14
  147. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +14 -14
  148. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +14 -14
  149. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +14 -14
  150. package/dist/astro/routes/api/schema/collections/index.mjs +14 -14
  151. package/dist/astro/routes/api/schema/index.mjs +5 -10
  152. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  153. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +14 -14
  154. package/dist/astro/routes/api/schema/orphans/index.mjs +14 -14
  155. package/dist/astro/routes/api/search/enable.mjs +5 -5
  156. package/dist/astro/routes/api/search/index.mjs +4 -4
  157. package/dist/astro/routes/api/search/rebuild.mjs +5 -5
  158. package/dist/astro/routes/api/search/stats.mjs +3 -3
  159. package/dist/astro/routes/api/search/suggest.mjs +4 -4
  160. package/dist/astro/routes/api/sections/_slug_.mjs +5 -5
  161. package/dist/astro/routes/api/sections/index.mjs +5 -5
  162. package/dist/astro/routes/api/settings/email.mjs +1 -1
  163. package/dist/astro/routes/api/settings.mjs +6 -6
  164. package/dist/astro/routes/api/setup/admin-verify.mjs +7 -7
  165. package/dist/astro/routes/api/setup/admin.mjs +6 -6
  166. package/dist/astro/routes/api/setup/dev-bypass.mjs +10 -10
  167. package/dist/astro/routes/api/setup/index.mjs +9 -9
  168. package/dist/astro/routes/api/setup/status.mjs +2 -2
  169. package/dist/astro/routes/api/snapshot.mjs +3 -3
  170. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +6 -6
  171. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +6 -6
  172. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +6 -6
  173. package/dist/astro/routes/api/taxonomies/index.mjs +6 -6
  174. package/dist/astro/routes/api/themes/preview.mjs +3 -3
  175. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  176. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  177. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  178. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +3 -3
  179. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -5
  180. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  181. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -5
  182. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  183. package/dist/astro/routes/api/widget-areas/_name_.mjs +4 -3
  184. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  185. package/dist/astro/routes/api/widget-areas/index.mjs +6 -5
  186. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  187. package/dist/astro/routes/api/widget-components.mjs +1 -1
  188. package/dist/astro/routes/robots.txt.mjs +3 -3
  189. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  190. package/dist/astro/routes/sitemap-_collection_.xml.mjs +12 -5
  191. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  192. package/dist/astro/routes/sitemap.xml.mjs +4 -4
  193. package/dist/astro/types.d.mts +12 -12
  194. package/dist/auth/providers/github.d.mts +1 -1
  195. package/dist/auth/providers/google.d.mts +1 -1
  196. package/dist/{authorize-C_8t2KGa.mjs → authorize-DsMSVSaY.mjs} +1 -1
  197. package/dist/{authorize-C_8t2KGa.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
  198. package/dist/{byline-fields-C_OsR-KF.mjs → byline-fields--WxSNS79.mjs} +1 -1
  199. package/dist/{byline-fields-C_OsR-KF.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
  200. package/dist/{byline-fields-51kg6Vuv.mjs → byline-fields-8TMtkBnH.mjs} +2 -2
  201. package/dist/{byline-fields-51kg6Vuv.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
  202. package/dist/{byline-fields-DYXKDuNX.d.mts → byline-fields-DbibsvTl.d.mts} +5 -1
  203. package/dist/byline-fields-DbibsvTl.d.mts.map +1 -0
  204. package/dist/{bylines-Cx5n-WqP.mjs → bylines-BdxWCnPL.mjs} +1 -1
  205. package/dist/{bylines-Cx5n-WqP.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
  206. package/dist/{bylines-wurS258E.mjs → bylines-s8c2DXbH.mjs} +3 -3
  207. package/dist/{bylines-wurS258E.mjs.map → bylines-s8c2DXbH.mjs.map} +1 -1
  208. package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-DXX3rfdI.mjs} +1 -1
  209. package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-DXX3rfdI.mjs.map} +1 -1
  210. package/dist/cli/index.mjs +11 -10
  211. package/dist/cli/index.mjs.map +1 -1
  212. package/dist/client/cf-access.d.mts +1 -1
  213. package/dist/client/index.d.mts +1 -1
  214. package/dist/client/index.mjs +1 -1
  215. package/dist/{comments-CJ0RZsYR.mjs → comments-Vkivawyl.mjs} +1 -1
  216. package/dist/{comments-CJ0RZsYR.mjs.map → comments-Vkivawyl.mjs.map} +1 -1
  217. package/dist/{components-CTfpu3PZ.mjs → components-CK0cuUoH.mjs} +1 -1
  218. package/dist/{components-CTfpu3PZ.mjs.map → components-CK0cuUoH.mjs.map} +1 -1
  219. package/dist/{context-GG52SPgh.mjs → context-Y7BRkWes.mjs} +2 -2
  220. package/dist/{context-GG52SPgh.mjs.map → context-Y7BRkWes.mjs.map} +1 -1
  221. package/dist/database/instrumentation.d.mts +10 -1
  222. package/dist/database/instrumentation.d.mts.map +1 -1
  223. package/dist/database/instrumentation.mjs +13 -1
  224. package/dist/database/instrumentation.mjs.map +1 -1
  225. package/dist/db/index.d.mts +3 -3
  226. package/dist/db/libsql.d.mts +1 -1
  227. package/dist/db/postgres.d.mts +1 -1
  228. package/dist/db/sqlite.d.mts +1 -1
  229. package/dist/{default-xLFNSsZ9.mjs → default-IlBaTFxM.mjs} +1 -1
  230. package/dist/{default-xLFNSsZ9.mjs.map → default-IlBaTFxM.mjs.map} +1 -1
  231. package/dist/{device-flow-s6_q3T7A.mjs → device-flow-R23SIbQ2.mjs} +4 -4
  232. package/dist/{device-flow-s6_q3T7A.mjs.map → device-flow-R23SIbQ2.mjs.map} +1 -1
  233. package/dist/{escape-bIyGoW5W.mjs → escape-Ds07EEyu.mjs} +1 -1
  234. package/dist/{escape-bIyGoW5W.mjs.map → escape-Ds07EEyu.mjs.map} +1 -1
  235. package/dist/{index-FfiTQJq2.d.mts → index-B1keaX5Y.d.mts} +43 -12
  236. package/dist/{index-FfiTQJq2.d.mts.map → index-B1keaX5Y.d.mts.map} +1 -1
  237. package/dist/{index-BpYeJO1E.d.mts → index-DR56od45.d.mts} +3 -3
  238. package/dist/{index-BpYeJO1E.d.mts.map → index-DR56od45.d.mts.map} +1 -1
  239. package/dist/index.d.mts +16 -16
  240. package/dist/index.mjs +22 -22
  241. package/dist/{load-B84ohfBk.mjs → load-BBetCvLC.mjs} +1 -1
  242. package/dist/{load-B84ohfBk.mjs.map → load-BBetCvLC.mjs.map} +1 -1
  243. package/dist/{loader-CpZKpFz0.mjs → loader-ZN1ll-d-.mjs} +11 -14
  244. package/dist/loader-ZN1ll-d-.mjs.map +1 -0
  245. package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-BtwbL_vj.mjs} +55 -2
  246. package/dist/manifest-schema-BtwbL_vj.mjs.map +1 -0
  247. package/dist/media/index.d.mts +1 -1
  248. package/dist/media/local-runtime.d.mts +11 -11
  249. package/dist/media/local-runtime.mjs +2 -2
  250. package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-Dknq-OFY.mjs} +1 -1
  251. package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-Dknq-OFY.mjs.map} +1 -1
  252. package/dist/media-url-VClf8glU.mjs +26 -0
  253. package/dist/media-url-VClf8glU.mjs.map +1 -0
  254. package/dist/{menus-Dp9xporj.mjs → menus-DrQLusqj.mjs} +6 -33
  255. package/dist/menus-DrQLusqj.mjs.map +1 -0
  256. package/dist/{mode-BjlXswIw.mjs → mode-CO2vQHfq.mjs} +1 -1
  257. package/dist/{mode-BjlXswIw.mjs.map → mode-CO2vQHfq.mjs.map} +1 -1
  258. package/dist/{oauth-authorization-1aPAYjiC.mjs → oauth-authorization-Bw4NdF_S.mjs} +4 -4
  259. package/dist/{oauth-authorization-1aPAYjiC.mjs.map → oauth-authorization-Bw4NdF_S.mjs.map} +1 -1
  260. package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BGGFp57s.mjs} +1 -1
  261. package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BGGFp57s.mjs.map} +1 -1
  262. package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-97x0xtN2.mjs} +1 -1
  263. package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-97x0xtN2.mjs.map} +1 -1
  264. package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-B_vnZHKO.mjs} +1 -1
  265. package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-B_vnZHKO.mjs.map} +1 -1
  266. package/dist/{options-D4MnavW_.d.mts → options-DyYIYpPd.d.mts} +3 -3
  267. package/dist/{options-D4MnavW_.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
  268. package/dist/page/index.d.mts +2 -2
  269. package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-C3QgnQnU.mjs} +1 -1
  270. package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-C3QgnQnU.mjs.map} +1 -1
  271. package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-CVBv5z8k.d.mts} +1 -1
  272. package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-CVBv5z8k.d.mts.map} +1 -1
  273. package/dist/plugin-types.d.mts +1 -1
  274. package/dist/plugin-utils.d.mts +9 -9
  275. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  276. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  277. package/dist/{public-url-egRHCy1m.mjs → public-url-BFVC2OTJ.mjs} +1 -1
  278. package/dist/{public-url-egRHCy1m.mjs.map → public-url-BFVC2OTJ.mjs.map} +1 -1
  279. package/dist/{query-BFQ029Ts.mjs → query-CbUcI4Xk.mjs} +18 -8
  280. package/dist/query-CbUcI4Xk.mjs.map +1 -0
  281. package/dist/{rate-limit-ClFFUga6.mjs → rate-limit-C7hjdkS5.mjs} +1 -1
  282. package/dist/{rate-limit-ClFFUga6.mjs.map → rate-limit-C7hjdkS5.mjs.map} +1 -1
  283. package/dist/{redirect-Cw3JTlmj.mjs → redirect-B_q19j4v.mjs} +1 -1
  284. package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-B_q19j4v.mjs.map} +1 -1
  285. package/dist/{redirects-DEygMrRO.mjs → redirects-CCbCqCCd.mjs} +4 -2
  286. package/dist/redirects-CCbCqCCd.mjs.map +1 -0
  287. package/dist/{redirects-OIu6vQ2i.mjs → redirects-DxVoR7PI.mjs} +1 -1
  288. package/dist/{redirects-OIu6vQ2i.mjs.map → redirects-DxVoR7PI.mjs.map} +1 -1
  289. package/dist/request-context.d.mts +7 -0
  290. package/dist/request-context.d.mts.map +1 -1
  291. package/dist/request-context.mjs +2 -1
  292. package/dist/request-context.mjs.map +1 -1
  293. package/dist/{runner-BcRuXq_h.d.mts → runner-DTdhuI9i.d.mts} +2 -2
  294. package/dist/{runner-BcRuXq_h.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
  295. package/dist/runtime.d.mts +10 -10
  296. package/dist/runtime.mjs +1 -1
  297. package/dist/{schema-CS7Eg5gh.mjs → schema-C1E70ug_.mjs} +2 -2
  298. package/dist/{schema-CS7Eg5gh.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
  299. package/dist/{search-o-aQzHI1.mjs → search-B3SGZw91.mjs} +2 -2
  300. package/dist/{search-o-aQzHI1.mjs.map → search-B3SGZw91.mjs.map} +1 -1
  301. package/dist/{secrets-C_ZtRos3.mjs → secrets-ChPTmy9x.mjs} +1 -1
  302. package/dist/{secrets-C_ZtRos3.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
  303. package/dist/{sections-DhsZ0ns9.mjs → sections-D_lVzwRZ.mjs} +2 -2
  304. package/dist/{sections-DhsZ0ns9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
  305. package/dist/seed/index.d.mts +2 -2
  306. package/dist/seed/index.mjs +6 -6
  307. package/dist/seo/index.d.mts +1 -1
  308. package/dist/seo/index.d.mts.map +1 -1
  309. package/dist/seo/index.mjs +3 -12
  310. package/dist/seo/index.mjs.map +1 -1
  311. package/dist/{seo-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
  312. package/dist/seo-D_LPkOtu.mjs.map +1 -0
  313. package/dist/{service-DAxg8RPR.mjs → service-ChDcsTBs.mjs} +2 -2
  314. package/dist/{service-DAxg8RPR.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
  315. package/dist/{settings-DIsbHTRE.mjs → settings-Cv47v9u8.mjs} +2 -2
  316. package/dist/{settings-DIsbHTRE.mjs.map → settings-Cv47v9u8.mjs.map} +1 -1
  317. package/dist/settings-DfxiWY_s.mjs +411 -0
  318. package/dist/settings-DfxiWY_s.mjs.map +1 -0
  319. package/dist/{setup-complete-Yuv78yua.mjs → setup-complete-yvPE4OsP.mjs} +1 -1
  320. package/dist/{setup-complete-Yuv78yua.mjs.map → setup-complete-yvPE4OsP.mjs.map} +1 -1
  321. package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-C9aFzb94.mjs} +1 -1
  322. package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-C9aFzb94.mjs.map} +1 -1
  323. package/dist/{site-url-mEVmwIFi.mjs → site-url-CnHlmAs9.mjs} +1 -1
  324. package/dist/{site-url-mEVmwIFi.mjs.map → site-url-CnHlmAs9.mjs.map} +1 -1
  325. package/dist/storage/local.d.mts +1 -1
  326. package/dist/storage/s3.d.mts +1 -1
  327. package/dist/{taxonomies-UusDXv3C.mjs → taxonomies-BILwiyGk.mjs} +2 -2
  328. package/dist/{taxonomies-UusDXv3C.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
  329. package/dist/{taxonomies-BEW7S5AI.mjs → taxonomies-BdAmbOwx.mjs} +46 -9
  330. package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
  331. package/dist/{transport-BwQeeY2p.d.mts → transport-B7PPP2CC.d.mts} +1 -1
  332. package/dist/{transport-BwQeeY2p.d.mts.map → transport-B7PPP2CC.d.mts.map} +1 -1
  333. package/dist/{transport--Ck3RBin.mjs → transport-CmpLD7W3.mjs} +1 -1
  334. package/dist/{transport--Ck3RBin.mjs.map → transport-CmpLD7W3.mjs.map} +1 -1
  335. package/dist/{types-DWnN7weG.d.mts → types-BFgrqwSk.d.mts} +1 -1
  336. package/dist/{types-DWnN7weG.d.mts.map → types-BFgrqwSk.d.mts.map} +1 -1
  337. package/dist/{types-Qa7-HJJC.d.mts → types-BH8-30hc.d.mts} +1 -1
  338. package/dist/{types-Qa7-HJJC.d.mts.map → types-BH8-30hc.d.mts.map} +1 -1
  339. package/dist/{types-OT_Es5mp.d.mts → types-BPzXTV9x.d.mts} +1 -1
  340. package/dist/{types-OT_Es5mp.d.mts.map → types-BPzXTV9x.d.mts.map} +1 -1
  341. package/dist/{types-DbCWhHet.d.mts → types-BUUVn1zr.d.mts} +2 -2
  342. package/dist/types-BUUVn1zr.d.mts.map +1 -0
  343. package/dist/{types-DMwSpvcw.d.mts → types-CPAPl93j.d.mts} +9 -3
  344. package/dist/{types-DMwSpvcw.d.mts.map → types-CPAPl93j.d.mts.map} +1 -1
  345. package/dist/types-CZI4E3qG.mjs +3 -0
  346. package/dist/{types-kwqCOUxj.d.mts → types-D4kUqbHh.d.mts} +1 -1
  347. package/dist/{types-kwqCOUxj.d.mts.map → types-D4kUqbHh.d.mts.map} +1 -1
  348. package/dist/{types-WVmpZBJV.d.mts → types-DTniiNto.d.mts} +2 -2
  349. package/dist/{types-WVmpZBJV.d.mts.map → types-DTniiNto.d.mts.map} +1 -1
  350. package/dist/types-DZk_y-MU.mjs.map +1 -1
  351. package/dist/{types-DX6v9KzJ.d.mts → types-S15DXXNi.d.mts} +1 -1
  352. package/dist/{types-DX6v9KzJ.d.mts.map → types-S15DXXNi.d.mts.map} +1 -1
  353. package/dist/{validate-ZP9Dvg0P.mjs → validate-Bz4vqcX1.mjs} +1 -1
  354. package/dist/{validate-ZP9Dvg0P.mjs.map → validate-Bz4vqcX1.mjs.map} +1 -1
  355. package/dist/{validate-BPAHUSge.d.mts → validate-CNwkPWzz.d.mts} +5 -5
  356. package/dist/{validate-BPAHUSge.d.mts.map → validate-CNwkPWzz.d.mts.map} +1 -1
  357. package/dist/{validation-CE5i4q0c.mjs → validation-DgGTJm3u.mjs} +1 -1
  358. package/dist/{validation-CE5i4q0c.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
  359. package/dist/version-D-5txk2m.mjs +7 -0
  360. package/dist/{version-Dw0JXu45.mjs.map → version-D-5txk2m.mjs.map} +1 -1
  361. package/dist/{widgets-ClEnYQCH.mjs → widgets-DZfmAbE4.mjs} +47 -44
  362. package/dist/widgets-DZfmAbE4.mjs.map +1 -0
  363. package/package.json +10 -10
  364. package/src/api/handlers/marketplace.ts +2 -5
  365. package/src/api/handlers/registry.ts +70 -0
  366. package/src/api/handlers/seo.ts +9 -1
  367. package/src/api/schemas/schema.ts +13 -1
  368. package/src/astro/middleware.ts +20 -6
  369. package/src/astro/routes/api/schema/index.ts +7 -15
  370. package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
  371. package/src/cli/commands/bundle-utils.ts +2 -0
  372. package/src/cli/commands/secrets.ts +2 -2
  373. package/src/database/instrumentation.ts +13 -0
  374. package/src/emdash-runtime.ts +31 -25
  375. package/src/loader.ts +24 -15
  376. package/src/plugins/manifest-schema.ts +75 -0
  377. package/src/plugins/marketplace.ts +2 -5
  378. package/src/plugins/types.ts +12 -0
  379. package/src/query.ts +13 -2
  380. package/src/request-context.ts +8 -0
  381. package/src/schema/types.ts +11 -1
  382. package/src/seo/index.ts +2 -28
  383. package/src/seo/media-url.ts +32 -0
  384. package/src/settings/index.ts +32 -40
  385. package/src/taxonomies/index.ts +78 -12
  386. package/src/utils/isolate-cache.ts +189 -0
  387. package/src/widgets/index.ts +57 -54
  388. package/dist/api-BZ6bhjYs.mjs.map +0 -1
  389. package/dist/byline-fields-DYXKDuNX.d.mts.map +0 -1
  390. package/dist/loader-CpZKpFz0.mjs.map +0 -1
  391. package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
  392. package/dist/menus-Dp9xporj.mjs.map +0 -1
  393. package/dist/query-BFQ029Ts.mjs.map +0 -1
  394. package/dist/redirects-DEygMrRO.mjs.map +0 -1
  395. package/dist/seo-DfjLvu8i.mjs.map +0 -1
  396. package/dist/settings-B1p-gPUK.mjs +0 -235
  397. package/dist/settings-B1p-gPUK.mjs.map +0 -1
  398. package/dist/taxonomies-BEW7S5AI.mjs.map +0 -1
  399. package/dist/types-Cj2S6FuC.mjs +0 -3
  400. package/dist/types-DbCWhHet.d.mts.map +0 -1
  401. package/dist/version-Dw0JXu45.mjs +0 -7
  402. package/dist/widgets-ClEnYQCH.mjs.map +0 -1
  403. /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-Oq39ba-Z.mjs} +0 -0
@@ -1,5 +1,6 @@
1
- import { r as getDb } from "./loader-CpZKpFz0.mjs";
2
- import { t as getWidgetComponents$1 } from "./components-CTfpu3PZ.mjs";
1
+ import { r as requestCached } from "./request-cache-D32LpnmI.mjs";
2
+ import { r as getDb } from "./loader-ZN1ll-d-.mjs";
3
+ import { t as getWidgetComponents$1 } from "./components-CK0cuUoH.mjs";
3
4
 
4
5
  //#region src/widgets/index.ts
5
6
  /**
@@ -10,48 +11,50 @@ import { t as getWidgetComponents$1 } from "./components-CTfpu3PZ.mjs";
10
11
  * row with null widget columns, which we skip when mapping.
11
12
  */
12
13
  async function getWidgetArea(name) {
13
- const rows = await (await getDb()).selectFrom("_emdash_widget_areas as a").leftJoin("_emdash_widgets as w", "w.area_id", "a.id").select([
14
- "a.id as a_id",
15
- "a.name as a_name",
16
- "a.label as a_label",
17
- "a.description as a_description",
18
- "w.id as w_id",
19
- "w.type as w_type",
20
- "w.title as w_title",
21
- "w.content as w_content",
22
- "w.menu_name as w_menu_name",
23
- "w.component_id as w_component_id",
24
- "w.component_props as w_component_props",
25
- "w.area_id as w_area_id",
26
- "w.sort_order as w_sort_order",
27
- "w.created_at as w_created_at"
28
- ]).where("a.name", "=", name).orderBy("w.sort_order", "asc").execute();
29
- const first = rows[0];
30
- if (!first) return null;
31
- const widgets = [];
32
- for (const row of rows) {
33
- if (row.w_id === null) continue;
34
- const widgetRow = {
35
- id: row.w_id,
36
- type: row.w_type,
37
- title: row.w_title,
38
- content: row.w_content,
39
- menu_name: row.w_menu_name,
40
- component_id: row.w_component_id,
41
- component_props: row.w_component_props,
42
- area_id: row.w_area_id,
43
- sort_order: row.w_sort_order,
44
- created_at: row.w_created_at
14
+ return requestCached(`widget-area:${name}`, async () => {
15
+ const rows = await (await getDb()).selectFrom("_emdash_widget_areas as a").leftJoin("_emdash_widgets as w", "w.area_id", "a.id").select([
16
+ "a.id as a_id",
17
+ "a.name as a_name",
18
+ "a.label as a_label",
19
+ "a.description as a_description",
20
+ "w.id as w_id",
21
+ "w.type as w_type",
22
+ "w.title as w_title",
23
+ "w.content as w_content",
24
+ "w.menu_name as w_menu_name",
25
+ "w.component_id as w_component_id",
26
+ "w.component_props as w_component_props",
27
+ "w.area_id as w_area_id",
28
+ "w.sort_order as w_sort_order",
29
+ "w.created_at as w_created_at"
30
+ ]).where("a.name", "=", name).orderBy("w.sort_order", "asc").execute();
31
+ const first = rows[0];
32
+ if (!first) return null;
33
+ const widgets = [];
34
+ for (const row of rows) {
35
+ if (row.w_id === null) continue;
36
+ const widgetRow = {
37
+ id: row.w_id,
38
+ type: row.w_type,
39
+ title: row.w_title,
40
+ content: row.w_content,
41
+ menu_name: row.w_menu_name,
42
+ component_id: row.w_component_id,
43
+ component_props: row.w_component_props,
44
+ area_id: row.w_area_id,
45
+ sort_order: row.w_sort_order,
46
+ created_at: row.w_created_at
47
+ };
48
+ widgets.push(rowToWidget(widgetRow));
49
+ }
50
+ return {
51
+ id: first.a_id,
52
+ name: first.a_name,
53
+ label: first.a_label,
54
+ description: first.a_description ?? void 0,
55
+ widgets
45
56
  };
46
- widgets.push(rowToWidget(widgetRow));
47
- }
48
- return {
49
- id: first.a_id,
50
- name: first.a_name,
51
- label: first.a_label,
52
- description: first.a_description ?? void 0,
53
- widgets
54
- };
57
+ });
55
58
  }
56
59
  /**
57
60
  * Get all widget areas with their widgets
@@ -103,4 +106,4 @@ function rowToWidget(row) {
103
106
 
104
107
  //#endregion
105
108
  export { rowToWidget as i, getWidgetAreas as n, getWidgetComponents as r, getWidgetArea as t };
106
- //# sourceMappingURL=widgets-ClEnYQCH.mjs.map
109
+ //# sourceMappingURL=widgets-DZfmAbE4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widgets-DZfmAbE4.mjs","names":["getComponentRegistry"],"sources":["../src/widgets/index.ts"],"sourcesContent":["import { getDb } from \"../loader.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { getWidgetComponents as getComponentRegistry } from \"./components.js\";\nimport type { Widget, WidgetArea, WidgetRow, WidgetComponentDef } from \"./types.js\";\n\nexport type {\n\tWidget,\n\tWidgetArea,\n\tWidgetType,\n\tWidgetComponentDef,\n\tPropDef,\n\tCreateWidgetAreaInput,\n\tCreateWidgetInput,\n\tUpdateWidgetInput,\n\tReorderWidgetsInput,\n} from \"./types.js\";\n\n/**\n * Get a widget area by name, with all its widgets.\n *\n * Single query with a left join rather than area-then-widgets so the\n * common case costs one round-trip. An area with no widgets yields one\n * row with null widget columns, which we skip when mapping.\n */\nexport async function getWidgetArea(name: string): Promise<WidgetArea | null> {\n\treturn requestCached(`widget-area:${name}`, async () => {\n\t\tconst db = await getDb();\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_widget_areas as a\")\n\t\t\t.leftJoin(\"_emdash_widgets as w\", \"w.area_id\", \"a.id\")\n\t\t\t.select([\n\t\t\t\t\"a.id as a_id\",\n\t\t\t\t\"a.name as a_name\",\n\t\t\t\t\"a.label as a_label\",\n\t\t\t\t\"a.description as a_description\",\n\t\t\t\t\"w.id as w_id\",\n\t\t\t\t\"w.type as w_type\",\n\t\t\t\t\"w.title as w_title\",\n\t\t\t\t\"w.content as w_content\",\n\t\t\t\t\"w.menu_name as w_menu_name\",\n\t\t\t\t\"w.component_id as w_component_id\",\n\t\t\t\t\"w.component_props as w_component_props\",\n\t\t\t\t\"w.area_id as w_area_id\",\n\t\t\t\t\"w.sort_order as w_sort_order\",\n\t\t\t\t\"w.created_at as w_created_at\",\n\t\t\t])\n\t\t\t.where(\"a.name\", \"=\", name)\n\t\t\t.orderBy(\"w.sort_order\", \"asc\")\n\t\t\t.execute();\n\n\t\tconst first = rows[0];\n\t\tif (!first) return null;\n\t\tconst widgets: Widget[] = [];\n\t\tfor (const row of rows) {\n\t\t\tif (row.w_id === null) continue; // area has no widgets (left-join null row)\n\t\t\t// Left-join makes every w_* column nullable in the type; at runtime\n\t\t\t// they're all non-null once w_id is (we match on widgets.area_id, so\n\t\t\t// a widget row always has the not-null columns filled). Cast is the\n\t\t\t// price of that structural fact.\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- left-join row is non-null when w_id is set; see above\n\t\t\tconst widgetRow = {\n\t\t\t\tid: row.w_id,\n\t\t\t\ttype: row.w_type,\n\t\t\t\ttitle: row.w_title,\n\t\t\t\tcontent: row.w_content,\n\t\t\t\tmenu_name: row.w_menu_name,\n\t\t\t\tcomponent_id: row.w_component_id,\n\t\t\t\tcomponent_props: row.w_component_props,\n\t\t\t\tarea_id: row.w_area_id,\n\t\t\t\tsort_order: row.w_sort_order,\n\t\t\t\tcreated_at: row.w_created_at,\n\t\t\t} as WidgetRow;\n\t\t\twidgets.push(rowToWidget(widgetRow));\n\t\t}\n\n\t\treturn {\n\t\t\tid: first.a_id,\n\t\t\tname: first.a_name,\n\t\t\tlabel: first.a_label,\n\t\t\tdescription: first.a_description ?? undefined,\n\t\t\twidgets,\n\t\t};\n\t});\n}\n\n/**\n * Get all widget areas with their widgets\n */\nexport async function getWidgetAreas(): Promise<WidgetArea[]> {\n\tconst db = await getDb();\n\t// Get all areas\n\tconst areaRows = await db.selectFrom(\"_emdash_widget_areas\").selectAll().execute();\n\n\t// Get all widgets\n\tconst widgetRows = await db\n\t\t.selectFrom(\"_emdash_widgets\")\n\t\t.selectAll()\n\t\t.$castTo<WidgetRow>()\n\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t.execute();\n\n\t// Group widgets by area\n\tconst widgetsByArea = new Map<string, Widget[]>();\n\tfor (const row of widgetRows) {\n\t\tif (!widgetsByArea.has(row.area_id)) {\n\t\t\twidgetsByArea.set(row.area_id, []);\n\t\t}\n\t\twidgetsByArea.get(row.area_id)!.push(rowToWidget(row));\n\t}\n\n\t// Combine\n\treturn areaRows.map((areaRow) => ({\n\t\tid: areaRow.id,\n\t\tname: areaRow.name,\n\t\tlabel: areaRow.label,\n\t\tdescription: areaRow.description ?? undefined,\n\t\twidgets: widgetsByArea.get(areaRow.id) || [],\n\t}));\n}\n\n/**\n * Get available widget components (for admin UI)\n */\nexport function getWidgetComponents(): WidgetComponentDef[] {\n\treturn getComponentRegistry();\n}\n\n/**\n * Convert a widget row to the API type\n */\nexport function rowToWidget(row: WidgetRow): Widget {\n\tconst widget: Widget = {\n\t\tid: row.id,\n\t\ttype: row.type,\n\t\ttitle: row.title ?? undefined,\n\t};\n\n\t// Type-specific fields\n\tif (row.type === \"content\" && row.content) {\n\t\ttry {\n\t\t\twidget.content = JSON.parse(row.content);\n\t\t} catch {\n\t\t\t// Invalid JSON, ignore\n\t\t}\n\t}\n\n\tif (row.type === \"menu\" && row.menu_name) {\n\t\twidget.menuName = row.menu_name;\n\t}\n\n\tif (row.type === \"component\" && row.component_id) {\n\t\twidget.componentId = row.component_id;\n\t\tif (row.component_props) {\n\t\t\ttry {\n\t\t\t\twidget.componentProps = JSON.parse(row.component_props);\n\t\t\t} catch {\n\t\t\t\t// Invalid JSON, ignore\n\t\t\t}\n\t\t}\n\t}\n\n\treturn widget;\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,eAAsB,cAAc,MAA0C;AAC7E,QAAO,cAAc,eAAe,QAAQ,YAAY;EAEvD,MAAM,OAAO,OADF,MAAM,OAAO,EAEtB,WAAW,4BAA4B,CACvC,SAAS,wBAAwB,aAAa,OAAO,CACrD,OAAO;GACP;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,CAAC,CACD,MAAM,UAAU,KAAK,KAAK,CAC1B,QAAQ,gBAAgB,MAAM,CAC9B,SAAS;EAEX,MAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,UAAoB,EAAE;AAC5B,OAAK,MAAM,OAAO,MAAM;AACvB,OAAI,IAAI,SAAS,KAAM;GAMvB,MAAM,YAAY;IACjB,IAAI,IAAI;IACR,MAAM,IAAI;IACV,OAAO,IAAI;IACX,SAAS,IAAI;IACb,WAAW,IAAI;IACf,cAAc,IAAI;IAClB,iBAAiB,IAAI;IACrB,SAAS,IAAI;IACb,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB;AACD,WAAQ,KAAK,YAAY,UAAU,CAAC;;AAGrC,SAAO;GACN,IAAI,MAAM;GACV,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,aAAa,MAAM,iBAAiB;GACpC;GACA;GACA;;;;;AAMH,eAAsB,iBAAwC;CAC7D,MAAM,KAAK,MAAM,OAAO;CAExB,MAAM,WAAW,MAAM,GAAG,WAAW,uBAAuB,CAAC,WAAW,CAAC,SAAS;CAGlF,MAAM,aAAa,MAAM,GACvB,WAAW,kBAAkB,CAC7B,WAAW,CACX,SAAoB,CACpB,QAAQ,cAAc,MAAM,CAC5B,SAAS;CAGX,MAAM,gCAAgB,IAAI,KAAuB;AACjD,MAAK,MAAM,OAAO,YAAY;AAC7B,MAAI,CAAC,cAAc,IAAI,IAAI,QAAQ,CAClC,eAAc,IAAI,IAAI,SAAS,EAAE,CAAC;AAEnC,gBAAc,IAAI,IAAI,QAAQ,CAAE,KAAK,YAAY,IAAI,CAAC;;AAIvD,QAAO,SAAS,KAAK,aAAa;EACjC,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,OAAO,QAAQ;EACf,aAAa,QAAQ,eAAe;EACpC,SAAS,cAAc,IAAI,QAAQ,GAAG,IAAI,EAAE;EAC5C,EAAE;;;;;AAMJ,SAAgB,sBAA4C;AAC3D,QAAOA,uBAAsB;;;;;AAM9B,SAAgB,YAAY,KAAwB;CACnD,MAAM,SAAiB;EACtB,IAAI,IAAI;EACR,MAAM,IAAI;EACV,OAAO,IAAI,SAAS;EACpB;AAGD,KAAI,IAAI,SAAS,aAAa,IAAI,QACjC,KAAI;AACH,SAAO,UAAU,KAAK,MAAM,IAAI,QAAQ;SACjC;AAKT,KAAI,IAAI,SAAS,UAAU,IAAI,UAC9B,QAAO,WAAW,IAAI;AAGvB,KAAI,IAAI,SAAS,eAAe,IAAI,cAAc;AACjD,SAAO,cAAc,IAAI;AACzB,MAAI,IAAI,gBACP,KAAI;AACH,UAAO,iBAAiB,KAAK,MAAM,IAAI,gBAAgB;UAChD;;AAMV,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emdash",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Astro-native CMS with WordPress migration support",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -178,7 +178,7 @@
178
178
  "#types": "./src/astro/types.js"
179
179
  },
180
180
  "dependencies": {
181
- "@atcute/lexicons": "^1.3.0",
181
+ "@atcute/lexicons": "^2.0.0",
182
182
  "@atcute/multibase": "^1.2.0",
183
183
  "@floating-ui/react": "^0.27.16",
184
184
  "@modelcontextprotocol/sdk": "^1.26.0",
@@ -217,12 +217,12 @@
217
217
  "ulidx": "^2.4.1",
218
218
  "upng-js": "^2.1.0",
219
219
  "zod": "^4.4.1",
220
- "@atcute/client": "^4.2.1",
221
- "@emdash-cms/admin": "0.19.0",
222
- "@emdash-cms/auth": "0.19.0",
223
- "@emdash-cms/gutenberg-to-portable-text": "0.19.0",
224
- "@emdash-cms/registry-client": "0.3.1",
225
- "@emdash-cms/plugin-types": "0.0.1"
220
+ "@atcute/client": "^5.0.0",
221
+ "@emdash-cms/admin": "0.20.0",
222
+ "@emdash-cms/auth": "0.20.0",
223
+ "@emdash-cms/gutenberg-to-portable-text": "0.20.0",
224
+ "@emdash-cms/plugin-types": "0.1.0",
225
+ "@emdash-cms/registry-client": "0.3.2"
226
226
  },
227
227
  "optionalDependencies": {
228
228
  "@libsql/kysely-libsql": "^0.4.0",
@@ -248,14 +248,14 @@
248
248
  "@types/react": "19.2.14",
249
249
  "@types/sanitize-html": "^2.16.0",
250
250
  "@types/sax": "^1.2.7",
251
- "@vitest/ui": "^4.1.7",
251
+ "@vitest/ui": "^4.1.8",
252
252
  "publint": "0.3.17",
253
253
  "tsdown": "0.20.3",
254
254
  "typescript": "^6.0.3",
255
255
  "vite": "^6.0.0",
256
256
  "vitest": "^4.1.5",
257
257
  "zod-openapi": "^5.4.6",
258
- "@emdash-cms/blocks": "0.19.0"
258
+ "@emdash-cms/blocks": "0.20.0"
259
259
  },
260
260
  "repository": {
261
261
  "type": "git",
@@ -9,7 +9,7 @@ import type { Kysely } from "kysely";
9
9
 
10
10
  import type { Database } from "../../database/types.js";
11
11
  import { validatePluginIdentifier } from "../../database/validate.js";
12
- import { pluginManifestSchema } from "../../plugins/manifest-schema.js";
12
+ import { pluginManifestSchema, reconcileManifestAccess } from "../../plugins/manifest-schema.js";
13
13
  import { normalizeManifestRoute } from "../../plugins/manifest-schema.js";
14
14
  import {
15
15
  createMarketplaceClient,
@@ -271,10 +271,7 @@ export async function loadBundleFromR2(
271
271
  const parsed: unknown = JSON.parse(manifestText);
272
272
  const result = pluginManifestSchema.safeParse(parsed);
273
273
  if (!result.success) return null;
274
- // Elements are validated as unknown[] by Zod; cast to PluginManifest
275
- // for the Element[] type (Block Kit validation happens at render time).
276
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time
277
- const manifest = result.data as unknown as PluginManifest;
274
+ const manifest = reconcileManifestAccess(result.data);
278
275
 
279
276
  // Try to load admin code (optional)
280
277
  let adminCode: string | undefined;
@@ -49,6 +49,8 @@ import { extractBundle } from "../../plugins/marketplace.js";
49
49
  import type { PluginBundle } from "../../plugins/marketplace.js";
50
50
  import type { SandboxRunner } from "../../plugins/sandbox/types.js";
51
51
  import { PluginStateRepository } from "../../plugins/state.js";
52
+ import { declaredAccessToCapabilities } from "../../plugins/types.js";
53
+ import type { DeclaredAccess } from "../../plugins/types.js";
52
54
  import {
53
55
  canonicalCapabilitiesForDriftCheck,
54
56
  coerceRegistryConfig,
@@ -70,6 +72,26 @@ import {
70
72
  storeBundleInR2,
71
73
  } from "./marketplace.js";
72
74
 
75
+ const RELEASE_EXTENSION_NSID = "com.emdashcms.experimental.package.releaseExtension";
76
+
77
+ /**
78
+ * Whether two `declaredAccess` blocks grant exactly the same enforced access --
79
+ * the same capabilities AND the same host allow-list. Both are lowered through
80
+ * the canonical converter so that constraint content (`allowedHosts`), not just
81
+ * the capability set, is part of the comparison. The capability-set consent
82
+ * gate is blind to host scope; this is what keeps a bundle from being installed
83
+ * with a wider (or simply different) host allow-list than its published record
84
+ * advertised and the user consented to.
85
+ */
86
+ export function enforcedAccessEqual(a: DeclaredAccess, b: DeclaredAccess): boolean {
87
+ const aa = declaredAccessToCapabilities(a);
88
+ const bb = declaredAccessToCapabilities(b);
89
+ return (
90
+ JSON.stringify(aa.capabilities.toSorted()) === JSON.stringify(bb.capabilities.toSorted()) &&
91
+ JSON.stringify(aa.allowedHosts.toSorted()) === JSON.stringify(bb.allowedHosts.toSorted())
92
+ );
93
+ }
94
+
73
95
  // ── Types ──────────────────────────────────────────────────────────
74
96
 
75
97
  export interface RegistryInstallInput {
@@ -999,6 +1021,31 @@ export async function handleRegistryInstall(
999
1021
  // marketplace plugins that happen to share the publisher's slug.
1000
1022
  bundle.manifest = { ...bundle.manifest, id: pluginId };
1001
1023
 
1024
+ // Integrity: the bundle that will run MUST declare exactly the access
1025
+ // the signed release record advertises. The consent dialog is driven
1026
+ // from the record's `declaredAccess`, so a bundle enforcing something
1027
+ // different -- a wider host allow-list, an extra capability -- would run
1028
+ // outside what the user reviewed. The capability-set consent gate below
1029
+ // is blind to constraint content (host scope), so compare the full
1030
+ // enforced access of record vs bundle here and refuse on any difference.
1031
+ const recordExt =
1032
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- extensions is the lexicon's open `unknown` map; narrow to read our own extension
1033
+ (release?.extensions as Record<string, { declaredAccess?: DeclaredAccess }> | undefined)?.[
1034
+ RELEASE_EXTENSION_NSID
1035
+ ];
1036
+ if (
1037
+ !enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})
1038
+ ) {
1039
+ return {
1040
+ success: false,
1041
+ error: {
1042
+ code: "DECLARED_ACCESS_DRIFT",
1043
+ message:
1044
+ "The plugin bundle declares different permissions than its published record. Installation refused.",
1045
+ },
1046
+ };
1047
+ }
1048
+
1002
1049
  // Capability consent gate: the admin MUST acknowledge the
1003
1050
  // capabilities the bundle's manifest actually declares before we
1004
1051
  // install it. The bundle manifest is the only source of truth
@@ -1488,6 +1535,29 @@ export async function handleRegistryUpdate(
1488
1535
  // and R2 layout stay in sync across install and update.
1489
1536
  bundle.manifest = { ...bundle.manifest, id: pluginId };
1490
1537
 
1538
+ // Integrity: same gate as install. The new bundle must declare exactly
1539
+ // the access its signed release record advertises. Without it, an update
1540
+ // that changes only the host scope (e.g. api.good.com -> evil.com) keeps
1541
+ // the capability set identical, sails through the escalation diff below,
1542
+ // and installs a bundle enforcing a scope the record never showed.
1543
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- extensions is the lexicon's open `unknown` map; narrow to read our own extension
1544
+ const updateRecordExtensions = signedRelease?.extensions as
1545
+ | Record<string, { declaredAccess?: DeclaredAccess }>
1546
+ | undefined;
1547
+ const recordExt = updateRecordExtensions?.[RELEASE_EXTENSION_NSID];
1548
+ if (
1549
+ !enforcedAccessEqual(recordExt?.declaredAccess ?? {}, bundle.manifest.declaredAccess ?? {})
1550
+ ) {
1551
+ return {
1552
+ success: false,
1553
+ error: {
1554
+ code: "DECLARED_ACCESS_DRIFT",
1555
+ message:
1556
+ "The plugin bundle declares different permissions than its published record. Update refused.",
1557
+ },
1558
+ };
1559
+ }
1560
+
1491
1561
  // Diff capabilities + route visibility against the currently
1492
1562
  // installed bundle. Loading from R2 keeps us honest: the diff is
1493
1563
  // against the bytes the sandbox is actually running, not whatever
@@ -29,6 +29,12 @@ export interface SitemapContentEntry {
29
29
  * alternates between siblings.
30
30
  */
31
31
  translationGroup: string | null;
32
+ /**
33
+ * Stored SEO image reference (`_emdash_seo.seo_image`), or null when
34
+ * the entry has no SEO image. The route resolves it to an absolute
35
+ * URL and emits it as an `<image:image>` sitemap entry.
36
+ */
37
+ image: string | null;
32
38
  }
33
39
 
34
40
  /** Per-collection sitemap data with entries and URL pattern */
@@ -106,8 +112,9 @@ export async function handleSitemapData(
106
112
  updated_at: string;
107
113
  locale: string;
108
114
  translation_group: string | null;
115
+ seo_image: string | null;
109
116
  }>`
110
- SELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group
117
+ SELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group, s.seo_image
111
118
  FROM ${sql.ref(tableName)} c
112
119
  LEFT JOIN _emdash_seo s
113
120
  ON s.collection = ${col.slug}
@@ -129,6 +136,7 @@ export async function handleSitemapData(
129
136
  updatedAt: row.updated_at,
130
137
  locale: row.locale,
131
138
  translationGroup: row.translation_group,
139
+ image: row.seo_image ?? null,
132
140
  });
133
141
  }
134
142
 
@@ -31,7 +31,19 @@ const fieldTypeValues = z.enum([
31
31
 
32
32
  const repeaterSubFieldSchema = z.object({
33
33
  slug: z.string().min(1).max(63).regex(slugPattern, "Invalid slug format"),
34
- type: z.enum(["string", "text", "number", "integer", "boolean", "datetime", "select"]),
34
+ // Keep in sync with REPEATER_SUB_FIELD_TYPES in schema/types.ts.
35
+ // ("url" was already a documented sub-field type but missing here.)
36
+ type: z.enum([
37
+ "string",
38
+ "text",
39
+ "url",
40
+ "number",
41
+ "integer",
42
+ "boolean",
43
+ "datetime",
44
+ "select",
45
+ "image",
46
+ ]),
35
47
  label: z.string().min(1),
36
48
  required: z.boolean().optional(),
37
49
  options: z.array(z.string()).optional(),
@@ -333,6 +333,9 @@ function pushMetricsTimings(
333
333
  timings.push({ name: "db.last", dur: metrics.dbLastOffset, desc: "Last query at" });
334
334
  }
335
335
  }
336
+ if (metrics.rpcCount > 0) {
337
+ timings.push({ name: "rpc.count", dur: metrics.rpcCount, desc: "DB round trips" });
338
+ }
336
339
  if (metrics.cacheHits + metrics.cacheMisses > 0) {
337
340
  timings.push({ name: "cache.hit", dur: metrics.cacheHits, desc: "Cache hits" });
338
341
  timings.push({ name: "cache.miss", dur: metrics.cacheMisses, desc: "Cache misses" });
@@ -510,9 +513,15 @@ export const onRequest = defineMiddleware(async (context, next) => {
510
513
  ? { ...parent, db: anonScoped.db }
511
514
  : { editMode: false, db: anonScoped.db, metrics };
512
515
  return runWithContext(ctx, async () => {
513
- const response = await runAnon();
514
- anonScoped.commit();
515
- return response;
516
+ // commit() in finally: the write reached the primary independently
517
+ // of render, so the bookmark cookie must be persisted even if
518
+ // render throws -- otherwise a write-then-failed-render leaves the
519
+ // next request able to read pre-write state off a lagging replica.
520
+ try {
521
+ return await runAnon();
522
+ } finally {
523
+ anonScoped.commit();
524
+ }
516
525
  });
517
526
  }
518
527
  return runAnon();
@@ -681,9 +690,14 @@ export const onRequest = defineMiddleware(async (context, next) => {
681
690
  ? { ...parent, db: scoped.db }
682
691
  : { editMode: false, db: scoped.db, metrics };
683
692
  return runWithContext(ctx, async () => {
684
- const response = await renderAndFinalize();
685
- scoped.commit();
686
- return response;
693
+ // commit() in finally: persist the bookmark cookie even if render
694
+ // throws -- the write already reached the primary, so a failed
695
+ // render must not strand the next request on a stale replica read.
696
+ try {
697
+ return await renderAndFinalize();
698
+ } finally {
699
+ scoped.commit();
700
+ }
687
701
  });
688
702
  }
689
703
 
@@ -13,7 +13,7 @@ import type { APIRoute } from "astro";
13
13
  import { hashString } from "emdash";
14
14
 
15
15
  import { requirePerm } from "#api/authorize.js";
16
- import { handleError, requireDb } from "#api/error.js";
16
+ import { apiSuccess, handleError, requireDb } from "#api/error.js";
17
17
  import { SchemaRegistry } from "#schema/registry.js";
18
18
  import { generateTypeScript } from "#schema/zod-generator.js";
19
19
 
@@ -89,20 +89,12 @@ import type { PortableTextBlock } from "emdash";
89
89
 
90
90
  const version = await hashString(JSON.stringify(schemaExport));
91
91
 
92
- return new Response(
93
- JSON.stringify({
94
- ...schemaExport,
95
- version,
96
- }),
97
- {
98
- status: 200,
99
- headers: {
100
- "Content-Type": "application/json",
101
- "Cache-Control": "private, no-store",
102
- "X-Schema-Version": version,
103
- },
104
- },
105
- );
92
+ const response = apiSuccess({
93
+ ...schemaExport,
94
+ version,
95
+ });
96
+ response.headers.set("X-Schema-Version", version);
97
+ return response;
106
98
  } catch (error) {
107
99
  return handleError(error, "Schema export failed", "SCHEMA_EXPORT_ERROR");
108
100
  }
@@ -23,6 +23,7 @@ import { getSiteSettingsWithDb } from "#settings/index.js";
23
23
 
24
24
  import { getI18nConfig, isI18nEnabled } from "../../i18n/config.js";
25
25
  import { interpolateUrlPattern, localizePath } from "../../i18n/resolve.js";
26
+ import { buildSeoImageUrl } from "../../seo/media-url.js";
26
27
 
27
28
  export const prerender = false;
28
29
 
@@ -112,8 +113,8 @@ export const GET: APIRoute = async ({ params, locals, url }) => {
112
113
  const lines: string[] = ['<?xml version="1.0" encoding="UTF-8"?>'];
113
114
  lines.push(
114
115
  useXhtml
115
- ? '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">'
116
- : '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
116
+ ? '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">'
117
+ : '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">',
117
118
  );
118
119
 
119
120
  const writeUrl = async (entry: Entry, siblings: Entry[] | null) => {
@@ -127,6 +128,16 @@ export const GET: APIRoute = async ({ params, locals, url }) => {
127
128
  lines.push(` <loc>${escapeXml(loc)}</loc>`);
128
129
  lines.push(` <lastmod>${escapeXml(entry.updatedAt)}</lastmod>`);
129
130
 
131
+ // Google image sitemap extension: advertise the entry's SEO
132
+ // image (the same "preferred image" used for og:image) so it
133
+ // can be discovered and indexed for Google Images.
134
+ if (entry.image) {
135
+ const imageLoc = buildSeoImageUrl(entry.image, siteUrl);
136
+ lines.push(" <image:image>");
137
+ lines.push(` <image:loc>${escapeXml(imageLoc)}</image:loc>`);
138
+ lines.push(" </image:image>");
139
+ }
140
+
130
141
  if (useXhtml && siblings && siblings.length > 1) {
131
142
  // Emit one xhtml:link per sibling (including self -- Google
132
143
  // recommends including the page's own hreflang annotation).
@@ -13,6 +13,7 @@ import { pipeline } from "node:stream/promises";
13
13
  import { imageSize } from "image-size";
14
14
  import { packTar } from "modern-tar/fs";
15
15
 
16
+ import { capabilitiesToDeclaredAccess } from "../../plugins/types.js";
16
17
  import type {
17
18
  PluginManifest,
18
19
  ResolvedPlugin,
@@ -151,6 +152,7 @@ export function extractManifest(plugin: ResolvedPlugin): PluginManifest {
151
152
  return {
152
153
  id: plugin.id,
153
154
  version: plugin.version,
155
+ declaredAccess: capabilitiesToDeclaredAccess(plugin.capabilities, plugin.allowedHosts),
154
156
  capabilities: plugin.capabilities,
155
157
  allowedHosts: plugin.allowedHosts,
156
158
  storage: plugin.storage,
@@ -4,7 +4,7 @@
4
4
  * Pure (no-DB) commands for working with EmDash secrets:
5
5
  *
6
6
  * - `emdash secrets generate` — emits a fresh `EMDASH_ENCRYPTION_KEY`.
7
- * Optionally writes it to `.dev.vars` (Workers) or `.env` (Node).
7
+ * Optionally writes it to a local-secrets file (`.env`).
8
8
  * - `emdash secrets fingerprint <key>` — prints the kid for a key,
9
9
  * useful in CI for verifying what's been deployed without exposing
10
10
  * the raw value.
@@ -87,7 +87,7 @@ const generateCommand = defineCommand({
87
87
  write: {
88
88
  type: "string",
89
89
  description:
90
- "Optional path to write the key to (e.g. .dev.vars or .env). " +
90
+ "Optional path to write the key to (e.g. .env). " +
91
91
  "Won't overwrite an existing entry without --force.",
92
92
  },
93
93
  force: {
@@ -110,3 +110,16 @@ function kyselyLog(event: LogEvent): void {
110
110
  export function kyselyLogOption(): Logger {
111
111
  return kyselyLog;
112
112
  }
113
+
114
+ /**
115
+ * Record physical database round trips for the current request.
116
+ *
117
+ * Called by backends that batch (the DO SQL driver coalesces same-turn SELECTs
118
+ * into one RPC), so we can see round-trip count separately from logical query
119
+ * count (`dbCount`, bumped by the Kysely log hook). No-op outside a request or
120
+ * when metrics aren't attached (e.g. migrations on the singleton).
121
+ */
122
+ export function recordRpc(count = 1): void {
123
+ const ctx = getRequestContext();
124
+ if (ctx?.metrics) ctx.metrics.rpcCount += count;
125
+ }
@@ -45,6 +45,7 @@ import type {
45
45
  import type { FieldType } from "./schema/types.js";
46
46
  import { hashString } from "./utils/hash.js";
47
47
  import { createInitLock, type InitLock, initWithLock } from "./utils/init-lock.js";
48
+ import { createIsolateCache, isolateCachedAsync } from "./utils/isolate-cache.js";
48
49
  import { COMMIT, VERSION } from "./version.js";
49
50
 
50
51
  const LEADING_SLASH_PATTERN = /^\//;
@@ -417,12 +418,12 @@ export class EmDashRuntime {
417
418
  private pluginStates: Map<string, string>;
418
419
 
419
420
  /**
420
- * Set to true after FTS indexes have been verified for this worker
421
- * lifetime so we don't re-scan on every admin request. See
422
- * ensureSearchHealthy().
421
+ * Isolate-lifetime guard so FTS indexes are verified at most once per
422
+ * worker rather than on every admin request. See ensureSearchHealthy().
423
+ * Uses the poison-immune isolate cache (never a shared awaitable promise)
424
+ * so a cancelled first caller can't wedge later ones.
423
425
  */
424
- private _searchHealthChecked = false;
425
- private _searchHealthPromise: Promise<void> | null = null;
426
+ private readonly _searchHealthCache = createIsolateCache<void>();
426
427
 
427
428
  /** Current hook pipeline. Use the `hooks` getter for external access. */
428
429
  get hooks(): HookPipeline {
@@ -2228,27 +2229,32 @@ export class EmDashRuntime {
2228
2229
  * defend against FTS not existing yet (pre-setup).
2229
2230
  */
2230
2231
  async ensureSearchHealthy(): Promise<void> {
2231
- if (this._searchHealthChecked) return;
2232
- if (this._searchHealthPromise) return this._searchHealthPromise;
2233
- if (!isSqlite(this._db)) {
2234
- this._searchHealthChecked = true;
2235
- return;
2232
+ // Non-SQLite has no FTS to verify; the check is a cheap synchronous
2233
+ // branch, no need to cache it.
2234
+ if (!isSqlite(this._db)) return;
2235
+ try {
2236
+ await isolateCachedAsync(
2237
+ this._searchHealthCache,
2238
+ async () => {
2239
+ try {
2240
+ const ftsManager = new FTSManager(this._db);
2241
+ const repaired = await ftsManager.verifyAndRepairAll();
2242
+ if (repaired > 0) {
2243
+ console.log(`Repaired ${repaired} corrupted FTS index(es)`);
2244
+ }
2245
+ } catch {
2246
+ // FTS tables may not exist yet (pre-setup). Non-fatal — cache
2247
+ // the "checked" state regardless so we don't re-scan.
2248
+ }
2249
+ },
2250
+ { anchor: (promise) => after(() => promise), ownerTimeoutMs: 30_000 },
2251
+ );
2252
+ } catch {
2253
+ // This check is best-effort and must never fail the calling request.
2254
+ // The inner body already swallows verify errors; this guards the
2255
+ // outer failure modes (owner timeout, waiter give-up) so a slow FTS
2256
+ // scan degrades to "unverified", not a 500 on admin/search routes.
2236
2257
  }
2237
- this._searchHealthPromise = (async () => {
2238
- try {
2239
- const ftsManager = new FTSManager(this._db);
2240
- const repaired = await ftsManager.verifyAndRepairAll();
2241
- if (repaired > 0) {
2242
- console.log(`Repaired ${repaired} corrupted FTS index(es)`);
2243
- }
2244
- } catch {
2245
- // FTS tables may not exist yet (pre-setup). Non-fatal.
2246
- } finally {
2247
- this._searchHealthChecked = true;
2248
- this._searchHealthPromise = null;
2249
- }
2250
- })();
2251
- return this._searchHealthPromise;
2252
2258
  }
2253
2259
 
2254
2260
  // =========================================================================