emdash 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/dist/{adapters-C5AWLJSD.d.mts → adapters-BzIHV3sw.d.mts} +1 -1
  2. package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BzIHV3sw.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-B1u7Qnvg.mjs} +2 -2
  4. package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-B1u7Qnvg.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +15 -15
  7. package/dist/api/schemas/index.d.mts +2 -2
  8. package/dist/api/schemas/index.mjs +3 -3
  9. package/dist/{api-Cs7DAACP.mjs → api-DStv36ik.mjs} +123 -20
  10. package/dist/api-DStv36ik.mjs.map +1 -0
  11. package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-DPfhPu5V.mjs} +2 -2
  12. package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-DPfhPu5V.mjs.map} +1 -1
  13. package/dist/{apply-BWMV4Zmw.mjs → apply-Dr7snAMT.mjs} +23 -23
  14. package/dist/apply-Dr7snAMT.mjs.map +1 -0
  15. package/dist/astro/index.d.mts +10 -10
  16. package/dist/astro/index.d.mts.map +1 -1
  17. package/dist/astro/index.mjs +115 -25
  18. package/dist/astro/index.mjs.map +1 -1
  19. package/dist/astro/middleware/auth.d.mts +9 -9
  20. package/dist/astro/middleware/auth.mjs +6 -6
  21. package/dist/astro/middleware/redirect.mjs +4 -4
  22. package/dist/astro/middleware/request-context.mjs +2 -2
  23. package/dist/astro/middleware/setup.mjs +1 -1
  24. package/dist/astro/middleware.d.mts +26 -4
  25. package/dist/astro/middleware.d.mts.map +1 -1
  26. package/dist/astro/middleware.mjs +242 -259
  27. package/dist/astro/middleware.mjs.map +1 -1
  28. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
  29. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
  30. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
  31. package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
  32. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +5 -5
  33. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
  34. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
  35. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
  36. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
  37. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
  38. package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
  39. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
  40. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  41. package/dist/astro/routes/api/admin/comments/bulk.mjs +8 -8
  42. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  43. package/dist/astro/routes/api/admin/comments/index.mjs +8 -8
  44. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +5 -5
  45. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +4 -4
  46. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
  47. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
  48. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  49. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +33 -33
  51. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +33 -33
  52. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +33 -33
  53. package/dist/astro/routes/api/admin/plugins/index.mjs +33 -33
  54. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  55. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +33 -33
  56. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +33 -33
  57. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +33 -33
  58. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +33 -33
  59. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +34 -34
  60. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +33 -33
  61. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +34 -34
  62. package/dist/astro/routes/api/admin/plugins/updates.mjs +33 -33
  63. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +33 -33
  64. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  65. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +33 -33
  66. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
  67. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  68. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  69. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
  70. package/dist/astro/routes/api/admin/users/index.mjs +5 -5
  71. package/dist/astro/routes/api/auth/dev-bypass.mjs +5 -5
  72. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  73. package/dist/astro/routes/api/auth/invite/complete.mjs +10 -10
  74. package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
  75. package/dist/astro/routes/api/auth/invite/register-options.mjs +9 -9
  76. package/dist/astro/routes/api/auth/logout.mjs +3 -3
  77. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
  78. package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
  79. package/dist/astro/routes/api/auth/me.mjs +6 -6
  80. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  81. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  82. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  83. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
  84. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  85. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -10
  86. package/dist/astro/routes/api/auth/passkey/register/options.mjs +9 -9
  87. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +10 -10
  88. package/dist/astro/routes/api/auth/passkey/verify.mjs +10 -10
  89. package/dist/astro/routes/api/auth/signup/complete.mjs +10 -10
  90. package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
  91. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  92. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
  93. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  94. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -5
  95. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs.map +1 -1
  96. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  97. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  98. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +8 -8
  99. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +9 -8
  100. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  101. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  103. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts.map +1 -1
  104. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +12 -10
  105. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  106. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
  107. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  108. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
  109. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs.map +1 -1
  110. package/dist/astro/routes/api/content/_collection_/_id_.mjs +9 -8
  111. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  112. package/dist/astro/routes/api/content/_collection_/authors.d.mts +8 -0
  113. package/dist/astro/routes/api/content/_collection_/authors.d.mts.map +1 -0
  114. package/dist/astro/routes/api/content/_collection_/authors.mjs +19 -0
  115. package/dist/astro/routes/api/content/_collection_/authors.mjs.map +1 -0
  116. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
  117. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
  118. package/dist/astro/routes/api/dashboard.mjs +7 -7
  119. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  120. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  121. package/dist/astro/routes/api/import/probe.mjs +6 -6
  122. package/dist/astro/routes/api/import/wordpress/analyze.mjs +4 -4
  123. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  124. package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
  125. package/dist/astro/routes/api/import/wordpress/media.mjs +6 -6
  126. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  127. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
  128. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  129. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
  130. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  131. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
  132. package/dist/astro/routes/api/manifest.mjs +4 -4
  133. package/dist/astro/routes/api/mcp.mjs +29 -29
  134. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -6
  135. package/dist/astro/routes/api/media/_id_.mjs +6 -6
  136. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  137. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  138. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  139. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  140. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  141. package/dist/astro/routes/api/media.mjs +8 -8
  142. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +7 -7
  143. package/dist/astro/routes/api/menus/_name_/items.mjs +7 -7
  144. package/dist/astro/routes/api/menus/_name_/reorder.mjs +7 -7
  145. package/dist/astro/routes/api/menus/_name_/translations.mjs +7 -7
  146. package/dist/astro/routes/api/menus/_name_.mjs +7 -7
  147. package/dist/astro/routes/api/menus/index.mjs +7 -7
  148. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  149. package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
  150. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  151. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  152. package/dist/astro/routes/api/oauth/register.mjs +3 -3
  153. package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
  154. package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
  155. package/dist/astro/routes/api/oauth/token.mjs +6 -6
  156. package/dist/astro/routes/api/openapi.json.mjs +17 -3
  157. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  158. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  159. package/dist/astro/routes/api/redirects/404s/index.mjs +9 -9
  160. package/dist/astro/routes/api/redirects/404s/summary.mjs +9 -9
  161. package/dist/astro/routes/api/redirects/_id_.mjs +10 -10
  162. package/dist/astro/routes/api/redirects/index.mjs +10 -10
  163. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  164. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  165. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +33 -33
  166. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +33 -33
  167. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +33 -33
  168. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +33 -33
  169. package/dist/astro/routes/api/schema/collections/index.mjs +33 -33
  170. package/dist/astro/routes/api/schema/index.mjs +9 -14
  171. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  172. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +33 -33
  173. package/dist/astro/routes/api/schema/orphans/index.mjs +33 -33
  174. package/dist/astro/routes/api/search/enable.mjs +9 -9
  175. package/dist/astro/routes/api/search/index.mjs +8 -8
  176. package/dist/astro/routes/api/search/rebuild.mjs +9 -9
  177. package/dist/astro/routes/api/search/stats.mjs +6 -6
  178. package/dist/astro/routes/api/search/suggest.mjs +8 -8
  179. package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
  180. package/dist/astro/routes/api/sections/index.mjs +8 -8
  181. package/dist/astro/routes/api/settings/email.mjs +5 -5
  182. package/dist/astro/routes/api/settings.mjs +12 -12
  183. package/dist/astro/routes/api/setup/admin-verify.mjs +11 -11
  184. package/dist/astro/routes/api/setup/admin.mjs +10 -10
  185. package/dist/astro/routes/api/setup/dev-bypass.mjs +23 -23
  186. package/dist/astro/routes/api/setup/dev-reset.mjs +3 -3
  187. package/dist/astro/routes/api/setup/index.mjs +23 -23
  188. package/dist/astro/routes/api/setup/status.mjs +4 -4
  189. package/dist/astro/routes/api/snapshot.mjs +6 -6
  190. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -11
  191. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
  192. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
  193. package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
  194. package/dist/astro/routes/api/themes/preview.mjs +6 -6
  195. package/dist/astro/routes/api/typegen.mjs +5 -5
  196. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  197. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  198. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  199. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -6
  200. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
  201. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  202. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
  203. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  204. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -5
  205. package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
  206. package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
  207. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  208. package/dist/astro/routes/api/widget-components.mjs +3 -3
  209. package/dist/astro/routes/robots.txt.mjs +7 -7
  210. package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
  211. package/dist/astro/routes/sitemap-_collection_.xml.mjs +16 -9
  212. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  213. package/dist/astro/routes/sitemap.xml.mjs +8 -8
  214. package/dist/astro/types.d.mts +19 -12
  215. package/dist/astro/types.d.mts.map +1 -1
  216. package/dist/auth/providers/github.d.mts +1 -1
  217. package/dist/auth/providers/google.d.mts +1 -1
  218. package/dist/{authorize-CotM4Yiu.mjs → authorize-DsMSVSaY.mjs} +2 -2
  219. package/dist/{authorize-CotM4Yiu.mjs.map → authorize-DsMSVSaY.mjs.map} +1 -1
  220. package/dist/{byline-CWQ9aSoz.mjs → byline-DUx48sJp.mjs} +6 -6
  221. package/dist/{byline-CWQ9aSoz.mjs.map → byline-DUx48sJp.mjs.map} +1 -1
  222. package/dist/{byline-fields-DC3Wkk-U.mjs → byline-fields--WxSNS79.mjs} +2 -2
  223. package/dist/{byline-fields-DC3Wkk-U.mjs.map → byline-fields--WxSNS79.mjs.map} +1 -1
  224. package/dist/{byline-fields-Dr-xcb6S.mjs → byline-fields-8TMtkBnH.mjs} +3 -3
  225. package/dist/{byline-fields-Dr-xcb6S.mjs.map → byline-fields-8TMtkBnH.mjs.map} +1 -1
  226. package/dist/{byline-fields-BNy7Ng1U.d.mts → byline-fields-DbibsvTl.d.mts} +30 -2
  227. package/dist/byline-fields-DbibsvTl.d.mts.map +1 -0
  228. package/dist/{byline-registry-CxK5g559.mjs → byline-registry-CWP7I71B.mjs} +3 -3
  229. package/dist/{byline-registry-CxK5g559.mjs.map → byline-registry-CWP7I71B.mjs.map} +1 -1
  230. package/dist/{bylines-LJMgENMI.mjs → bylines-BdxWCnPL.mjs} +3 -3
  231. package/dist/{bylines-LJMgENMI.mjs.map → bylines-BdxWCnPL.mjs.map} +1 -1
  232. package/dist/{bylines-BJSva1Un.mjs → bylines-s8c2DXbH.mjs} +50 -6
  233. package/dist/{bylines-BJSva1Un.mjs.map → bylines-s8c2DXbH.mjs.map} +1 -1
  234. package/dist/{cache-lZL7SgVb.mjs → cache-B_HzASVT.mjs} +3 -3
  235. package/dist/{cache-lZL7SgVb.mjs.map → cache-B_HzASVT.mjs.map} +1 -1
  236. package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-DXX3rfdI.mjs} +1 -1
  237. package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-DXX3rfdI.mjs.map} +1 -1
  238. package/dist/{chunks-BU-vP9Dh.mjs → chunks-BerYVuve.mjs} +2 -2
  239. package/dist/{chunks-BU-vP9Dh.mjs.map → chunks-BerYVuve.mjs.map} +1 -1
  240. package/dist/cli/index.mjs +46 -32
  241. package/dist/cli/index.mjs.map +1 -1
  242. package/dist/client/cf-access.d.mts +1 -1
  243. package/dist/client/index.d.mts +1 -1
  244. package/dist/client/index.mjs +1 -1
  245. package/dist/{comment-C4jVbCM8.mjs → comment-sqQxNpN3.mjs} +2 -2
  246. package/dist/{comment-C4jVbCM8.mjs.map → comment-sqQxNpN3.mjs.map} +1 -1
  247. package/dist/{comments-BTAbC0Ek.mjs → comments-Vkivawyl.mjs} +3 -3
  248. package/dist/{comments-BTAbC0Ek.mjs.map → comments-Vkivawyl.mjs.map} +1 -1
  249. package/dist/{components-CTfpu3PZ.mjs → components-CK0cuUoH.mjs} +1 -1
  250. package/dist/{components-CTfpu3PZ.mjs.map → components-CK0cuUoH.mjs.map} +1 -1
  251. package/dist/{content-CyqOmOzm.mjs → content-BIlVx-RX.mjs} +132 -43
  252. package/dist/content-BIlVx-RX.mjs.map +1 -0
  253. package/dist/{context-DZ7bEh5-.mjs → context-Y7BRkWes.mjs} +10 -10
  254. package/dist/{context-DZ7bEh5-.mjs.map → context-Y7BRkWes.mjs.map} +1 -1
  255. package/dist/{cron-DZovZUnC.mjs → cron-BJ2ClIlj.mjs} +4 -3
  256. package/dist/cron-BJ2ClIlj.mjs.map +1 -0
  257. package/dist/{dashboard-B5WQpNTP.mjs → dashboard-2JgAMWxK.mjs} +4 -4
  258. package/dist/{dashboard-B5WQpNTP.mjs.map → dashboard-2JgAMWxK.mjs.map} +1 -1
  259. package/dist/database/instrumentation.d.mts +10 -1
  260. package/dist/database/instrumentation.d.mts.map +1 -1
  261. package/dist/database/instrumentation.mjs +13 -1
  262. package/dist/database/instrumentation.mjs.map +1 -1
  263. package/dist/db/index.d.mts +3 -3
  264. package/dist/db/index.mjs +1 -1
  265. package/dist/db/libsql.d.mts +1 -1
  266. package/dist/db/postgres.d.mts +1 -1
  267. package/dist/db/sqlite.d.mts +1 -1
  268. package/dist/{default-xLFNSsZ9.mjs → default-IlBaTFxM.mjs} +1 -1
  269. package/dist/{default-xLFNSsZ9.mjs.map → default-IlBaTFxM.mjs.map} +1 -1
  270. package/dist/{device-flow-ptLrVINd.mjs → device-flow-R23SIbQ2.mjs} +5 -5
  271. package/dist/{device-flow-ptLrVINd.mjs.map → device-flow-R23SIbQ2.mjs.map} +1 -1
  272. package/dist/{error-DJOsMVSt.mjs → error-RwM4dD35.mjs} +2 -2
  273. package/dist/{error-DJOsMVSt.mjs.map → error-RwM4dD35.mjs.map} +1 -1
  274. package/dist/{escape-bIyGoW5W.mjs → escape-Ds07EEyu.mjs} +1 -1
  275. package/dist/{escape-bIyGoW5W.mjs.map → escape-Ds07EEyu.mjs.map} +1 -1
  276. package/dist/{fts-manager-DR1ERA0c.mjs → fts-manager-1RgHmopc.mjs} +2 -2
  277. package/dist/{fts-manager-DR1ERA0c.mjs.map → fts-manager-1RgHmopc.mjs.map} +1 -1
  278. package/dist/{index-CjKdMZ3U.d.mts → index-B1keaX5Y.d.mts} +237 -24
  279. package/dist/index-B1keaX5Y.d.mts.map +1 -0
  280. package/dist/{index-D60_SzHG.d.mts → index-DR56od45.d.mts} +3 -3
  281. package/dist/{index-D60_SzHG.d.mts.map → index-DR56od45.d.mts.map} +1 -1
  282. package/dist/index.d.mts +17 -17
  283. package/dist/index.mjs +46 -46
  284. package/dist/{load-6ZrRhepW.mjs → load-BBetCvLC.mjs} +2 -2
  285. package/dist/{load-6ZrRhepW.mjs.map → load-BBetCvLC.mjs.map} +1 -1
  286. package/dist/{loader-Dyx8dhFV.mjs → loader-ZN1ll-d-.mjs} +36 -37
  287. package/dist/loader-ZN1ll-d-.mjs.map +1 -0
  288. package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-BtwbL_vj.mjs} +55 -2
  289. package/dist/manifest-schema-BtwbL_vj.mjs.map +1 -0
  290. package/dist/media/index.d.mts +1 -1
  291. package/dist/media/index.mjs +1 -1
  292. package/dist/media/local-runtime.d.mts +11 -11
  293. package/dist/media/local-runtime.mjs +6 -6
  294. package/dist/{media-C-oovGCG.mjs → media-JOf3pNkw.mjs} +2 -2
  295. package/dist/{media-C-oovGCG.mjs.map → media-JOf3pNkw.mjs.map} +1 -1
  296. package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-Dknq-OFY.mjs} +1 -1
  297. package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-Dknq-OFY.mjs.map} +1 -1
  298. package/dist/media-url-VClf8glU.mjs +26 -0
  299. package/dist/media-url-VClf8glU.mjs.map +1 -0
  300. package/dist/{menus-DugoYwTX.mjs → menus-DX4_E01q.mjs} +3 -3
  301. package/dist/{menus-DugoYwTX.mjs.map → menus-DX4_E01q.mjs.map} +1 -1
  302. package/dist/{menus-BKkxXCmd.mjs → menus-DrQLusqj.mjs} +87 -37
  303. package/dist/menus-DrQLusqj.mjs.map +1 -0
  304. package/dist/{mode-BjlXswIw.mjs → mode-CO2vQHfq.mjs} +1 -1
  305. package/dist/{mode-BjlXswIw.mjs.map → mode-CO2vQHfq.mjs.map} +1 -1
  306. package/dist/{normalize-DVV8nbrL.mjs → normalize-CK5o04zr.mjs} +2 -2
  307. package/dist/{normalize-DVV8nbrL.mjs.map → normalize-CK5o04zr.mjs.map} +1 -1
  308. package/dist/{oauth-authorization-DvBAL75d.mjs → oauth-authorization-Bw4NdF_S.mjs} +5 -5
  309. package/dist/{oauth-authorization-DvBAL75d.mjs.map → oauth-authorization-Bw4NdF_S.mjs.map} +1 -1
  310. package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BGGFp57s.mjs} +1 -1
  311. package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BGGFp57s.mjs.map} +1 -1
  312. package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-97x0xtN2.mjs} +1 -1
  313. package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-97x0xtN2.mjs.map} +1 -1
  314. package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-B_vnZHKO.mjs} +1 -1
  315. package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-B_vnZHKO.mjs.map} +1 -1
  316. package/dist/{options-BL4X94qY.mjs → options-BPCVnesz.mjs} +1 -1
  317. package/dist/{options-BL4X94qY.mjs.map → options-BPCVnesz.mjs.map} +1 -1
  318. package/dist/{options-tb7DJROi.d.mts → options-DyYIYpPd.d.mts} +3 -3
  319. package/dist/{options-tb7DJROi.d.mts.map → options-DyYIYpPd.d.mts.map} +1 -1
  320. package/dist/page/index.d.mts +2 -2
  321. package/dist/{parse-BBkFmLVr.mjs → parse-CrGndy1A.mjs} +2 -2
  322. package/dist/{parse-BBkFmLVr.mjs.map → parse-CrGndy1A.mjs.map} +1 -1
  323. package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-C3QgnQnU.mjs} +1 -1
  324. package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-C3QgnQnU.mjs.map} +1 -1
  325. package/dist/{patterns-CqG5Ya3i.mjs → patterns-p-RBdTbM.mjs} +1 -1
  326. package/dist/{patterns-CqG5Ya3i.mjs.map → patterns-p-RBdTbM.mjs.map} +1 -1
  327. package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-CVBv5z8k.d.mts} +1 -1
  328. package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-CVBv5z8k.d.mts.map} +1 -1
  329. package/dist/plugin-types.d.mts +1 -1
  330. package/dist/plugin-utils.d.mts +9 -9
  331. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  332. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  333. package/dist/{public-url-egRHCy1m.mjs → public-url-BFVC2OTJ.mjs} +1 -1
  334. package/dist/{public-url-egRHCy1m.mjs.map → public-url-BFVC2OTJ.mjs.map} +1 -1
  335. package/dist/{query-Ctlq1aOk.mjs → query-CbUcI4Xk.mjs} +33 -17
  336. package/dist/query-CbUcI4Xk.mjs.map +1 -0
  337. package/dist/{rate-limit-CH6W6ikK.mjs → rate-limit-C7hjdkS5.mjs} +2 -2
  338. package/dist/{rate-limit-CH6W6ikK.mjs.map → rate-limit-C7hjdkS5.mjs.map} +1 -1
  339. package/dist/{redirect-Cw3JTlmj.mjs → redirect-B_q19j4v.mjs} +1 -1
  340. package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-B_q19j4v.mjs.map} +1 -1
  341. package/dist/{redirect-C6tJA7tk.mjs → redirect-CRWIt8Zj.mjs} +3 -3
  342. package/dist/{redirect-C6tJA7tk.mjs.map → redirect-CRWIt8Zj.mjs.map} +1 -1
  343. package/dist/{redirects-C0L9JUk4.mjs → redirects-CCbCqCCd.mjs} +28 -4
  344. package/dist/redirects-CCbCqCCd.mjs.map +1 -0
  345. package/dist/{redirects-CacE9eQa.mjs → redirects-DxVoR7PI.mjs} +5 -5
  346. package/dist/{redirects-CacE9eQa.mjs.map → redirects-DxVoR7PI.mjs.map} +1 -1
  347. package/dist/{registry-CIDxZbhh.mjs → registry-brYh-rAT.mjs} +6 -6
  348. package/dist/{registry-CIDxZbhh.mjs.map → registry-brYh-rAT.mjs.map} +1 -1
  349. package/dist/{request-cache-BYMs-BGX.mjs → request-cache-D32LpnmI.mjs} +1 -1
  350. package/dist/{request-cache-BYMs-BGX.mjs.map → request-cache-D32LpnmI.mjs.map} +1 -1
  351. package/dist/request-context.d.mts +7 -0
  352. package/dist/request-context.d.mts.map +1 -1
  353. package/dist/request-context.mjs +2 -1
  354. package/dist/request-context.mjs.map +1 -1
  355. package/dist/{runner-pt6Wl-l-.mjs → runner--4wMWwKM.mjs} +217 -166
  356. package/dist/runner--4wMWwKM.mjs.map +1 -0
  357. package/dist/{runner-DM1yR5qd.d.mts → runner-DTdhuI9i.d.mts} +2 -2
  358. package/dist/{runner-DM1yR5qd.d.mts.map → runner-DTdhuI9i.d.mts.map} +1 -1
  359. package/dist/runtime.d.mts +10 -10
  360. package/dist/runtime.mjs +2 -2
  361. package/dist/{schema-B4tk0HAG.mjs → schema-C1E70ug_.mjs} +5 -5
  362. package/dist/{schema-B4tk0HAG.mjs.map → schema-C1E70ug_.mjs.map} +1 -1
  363. package/dist/{search-f-fNfwab.mjs → search-B3SGZw91.mjs} +4 -4
  364. package/dist/{search-f-fNfwab.mjs.map → search-B3SGZw91.mjs.map} +1 -1
  365. package/dist/{secrets-YYbTgB1w.mjs → secrets-ChPTmy9x.mjs} +2 -2
  366. package/dist/{secrets-YYbTgB1w.mjs.map → secrets-ChPTmy9x.mjs.map} +1 -1
  367. package/dist/{sections-biElLfT9.mjs → sections-D_lVzwRZ.mjs} +3 -3
  368. package/dist/{sections-biElLfT9.mjs.map → sections-D_lVzwRZ.mjs.map} +1 -1
  369. package/dist/seed/index.d.mts +2 -2
  370. package/dist/seed/index.mjs +17 -17
  371. package/dist/seo/index.d.mts +1 -1
  372. package/dist/seo/index.d.mts.map +1 -1
  373. package/dist/seo/index.mjs +3 -12
  374. package/dist/seo/index.mjs.map +1 -1
  375. package/dist/{seo-BR39kvTF.mjs → seo-B5e6y9Wk.mjs} +2 -2
  376. package/dist/{seo-BR39kvTF.mjs.map → seo-B5e6y9Wk.mjs.map} +1 -1
  377. package/dist/{seo-DfjLvu8i.mjs → seo-D_LPkOtu.mjs} +4 -3
  378. package/dist/seo-D_LPkOtu.mjs.map +1 -0
  379. package/dist/{service-BhR2acnc.mjs → service-ChDcsTBs.mjs} +3 -3
  380. package/dist/{service-BhR2acnc.mjs.map → service-ChDcsTBs.mjs.map} +1 -1
  381. package/dist/{settings-D_NJvjgN.mjs → settings-Cv47v9u8.mjs} +3 -3
  382. package/dist/{settings-D_NJvjgN.mjs.map → settings-Cv47v9u8.mjs.map} +1 -1
  383. package/dist/settings-DfxiWY_s.mjs +411 -0
  384. package/dist/settings-DfxiWY_s.mjs.map +1 -0
  385. package/dist/{setup-complete-VoEZfasi.mjs → setup-complete-yvPE4OsP.mjs} +2 -2
  386. package/dist/{setup-complete-VoEZfasi.mjs.map → setup-complete-yvPE4OsP.mjs.map} +1 -1
  387. package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-C9aFzb94.mjs} +1 -1
  388. package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-C9aFzb94.mjs.map} +1 -1
  389. package/dist/{site-url-Cm8-sJy7.mjs → site-url-CnHlmAs9.mjs} +2 -2
  390. package/dist/{site-url-Cm8-sJy7.mjs.map → site-url-CnHlmAs9.mjs.map} +1 -1
  391. package/dist/storage/local.d.mts +1 -1
  392. package/dist/storage/s3.d.mts +1 -1
  393. package/dist/{taxonomies-Mhn9rjTQ.mjs → taxonomies-BILwiyGk.mjs} +4 -4
  394. package/dist/{taxonomies-Mhn9rjTQ.mjs.map → taxonomies-BILwiyGk.mjs.map} +1 -1
  395. package/dist/{taxonomies-Crtzy4MT.mjs → taxonomies-BdAmbOwx.mjs} +50 -12
  396. package/dist/taxonomies-BdAmbOwx.mjs.map +1 -0
  397. package/dist/{taxonomy-DTZrIQpi.mjs → taxonomy-CdllE4oq.mjs} +3 -3
  398. package/dist/{taxonomy-DTZrIQpi.mjs.map → taxonomy-CdllE4oq.mjs.map} +1 -1
  399. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-x2tJQ-A1.mjs} +1 -1
  400. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-x2tJQ-A1.mjs.map} +1 -1
  401. package/dist/{transport-OnMNbsIA.d.mts → transport-B7PPP2CC.d.mts} +1 -1
  402. package/dist/{transport-OnMNbsIA.d.mts.map → transport-B7PPP2CC.d.mts.map} +1 -1
  403. package/dist/{transport--Ck3RBin.mjs → transport-CmpLD7W3.mjs} +1 -1
  404. package/dist/{transport--Ck3RBin.mjs.map → transport-CmpLD7W3.mjs.map} +1 -1
  405. package/dist/{types-DWnN7weG.d.mts → types-BFgrqwSk.d.mts} +1 -1
  406. package/dist/{types-DWnN7weG.d.mts.map → types-BFgrqwSk.d.mts.map} +1 -1
  407. package/dist/{types-Qa7-HJJC.d.mts → types-BH8-30hc.d.mts} +1 -1
  408. package/dist/{types-Qa7-HJJC.d.mts.map → types-BH8-30hc.d.mts.map} +1 -1
  409. package/dist/{types-DawhLFwy.d.mts → types-BPzXTV9x.d.mts} +26 -1
  410. package/dist/{types-DawhLFwy.d.mts.map → types-BPzXTV9x.d.mts.map} +1 -1
  411. package/dist/{types-DbCWhHet.d.mts → types-BUUVn1zr.d.mts} +2 -2
  412. package/dist/types-BUUVn1zr.d.mts.map +1 -0
  413. package/dist/{types-K3MDsxpy.mjs → types-BXSUSAjt.mjs} +16 -3
  414. package/dist/{types-K3MDsxpy.mjs.map → types-BXSUSAjt.mjs.map} +1 -1
  415. package/dist/{types-DMwSpvcw.d.mts → types-CPAPl93j.d.mts} +9 -3
  416. package/dist/{types-DMwSpvcw.d.mts.map → types-CPAPl93j.d.mts.map} +1 -1
  417. package/dist/types-CZI4E3qG.mjs +3 -0
  418. package/dist/{types-kwqCOUxj.d.mts → types-D4kUqbHh.d.mts} +1 -1
  419. package/dist/{types-kwqCOUxj.d.mts.map → types-D4kUqbHh.d.mts.map} +1 -1
  420. package/dist/{types-i8_uzhMD.d.mts → types-DTniiNto.d.mts} +19 -4
  421. package/dist/types-DTniiNto.d.mts.map +1 -0
  422. package/dist/{types-D8bhH891.mjs → types-DZk_y-MU.mjs} +1 -1
  423. package/dist/types-DZk_y-MU.mjs.map +1 -0
  424. package/dist/{types-DX6v9KzJ.d.mts → types-S15DXXNi.d.mts} +1 -1
  425. package/dist/{types-DX6v9KzJ.d.mts.map → types-S15DXXNi.d.mts.map} +1 -1
  426. package/dist/{user-DzEUl5zA.mjs → user-C0um7wrg.mjs} +18 -2
  427. package/dist/user-C0um7wrg.mjs.map +1 -0
  428. package/dist/{validate-JCXcsqiY.mjs → validate-Bz4vqcX1.mjs} +6 -3
  429. package/dist/validate-Bz4vqcX1.mjs.map +1 -0
  430. package/dist/{validate-Dy6nkNls.d.mts → validate-CNwkPWzz.d.mts} +13 -5
  431. package/dist/validate-CNwkPWzz.d.mts.map +1 -0
  432. package/dist/{validation-Bq-VyKJg.mjs → validation-DgGTJm3u.mjs} +5 -5
  433. package/dist/{validation-Bq-VyKJg.mjs.map → validation-DgGTJm3u.mjs.map} +1 -1
  434. package/dist/version-D-5txk2m.mjs +7 -0
  435. package/dist/{version-CnS-Cr8A.mjs.map → version-D-5txk2m.mjs.map} +1 -1
  436. package/dist/{widgets-Bap1eS1X.mjs → widgets-DZfmAbE4.mjs} +47 -44
  437. package/dist/widgets-DZfmAbE4.mjs.map +1 -0
  438. package/dist/{zod-generator-BSDpkqSH.mjs → zod-generator-Djo_VHCt.mjs} +2 -2
  439. package/dist/{zod-generator-BSDpkqSH.mjs.map → zod-generator-Djo_VHCt.mjs.map} +1 -1
  440. package/package.json +10 -10
  441. package/src/api/handlers/content.ts +107 -8
  442. package/src/api/handlers/index.ts +2 -0
  443. package/src/api/handlers/marketplace.ts +2 -5
  444. package/src/api/handlers/registry.ts +70 -0
  445. package/src/api/handlers/seo.ts +9 -1
  446. package/src/api/openapi/document.ts +25 -0
  447. package/src/api/schemas/content.ts +33 -0
  448. package/src/api/schemas/schema.ts +13 -1
  449. package/src/astro/integration/index.ts +98 -0
  450. package/src/astro/integration/routes.ts +6 -0
  451. package/src/astro/integration/virtual-modules.ts +39 -0
  452. package/src/astro/integration/vite-config.ts +12 -0
  453. package/src/astro/middleware.ts +48 -6
  454. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  455. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +4 -2
  456. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +8 -4
  457. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +4 -2
  458. package/src/astro/routes/api/content/[collection]/[id].ts +4 -2
  459. package/src/astro/routes/api/content/[collection]/authors.ts +34 -0
  460. package/src/astro/routes/api/schema/index.ts +7 -15
  461. package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
  462. package/src/astro/types.ts +8 -1
  463. package/src/bylines/index.ts +57 -0
  464. package/src/cli/commands/bundle-utils.ts +2 -0
  465. package/src/cli/commands/export-seed.ts +28 -12
  466. package/src/cli/commands/secrets.ts +2 -2
  467. package/src/components/EmDashImage.astro +22 -4
  468. package/src/components/Image.astro +20 -3
  469. package/src/database/instrumentation.ts +13 -0
  470. package/src/database/migrations/043_content_references.ts +121 -0
  471. package/src/database/migrations/runner.ts +2 -0
  472. package/src/database/repositories/content.ts +225 -67
  473. package/src/database/repositories/index.ts +7 -0
  474. package/src/database/repositories/relation.ts +467 -0
  475. package/src/database/repositories/types.ts +31 -0
  476. package/src/database/repositories/user.ts +18 -0
  477. package/src/database/types.ts +34 -0
  478. package/src/emdash-runtime.ts +172 -67
  479. package/src/index.ts +8 -1
  480. package/src/loader.ts +81 -39
  481. package/src/media/responsive.ts +125 -0
  482. package/src/plugins/cron.ts +3 -2
  483. package/src/plugins/index.ts +5 -0
  484. package/src/plugins/manifest-schema.ts +75 -0
  485. package/src/plugins/marketplace.ts +2 -5
  486. package/src/plugins/scheduler/node.ts +9 -2
  487. package/src/plugins/types.ts +12 -0
  488. package/src/query.ts +45 -7
  489. package/src/request-context.ts +8 -0
  490. package/src/scheduled-publish.ts +153 -0
  491. package/src/schema/types.ts +11 -1
  492. package/src/seed/apply.ts +16 -6
  493. package/src/seed/types.ts +9 -0
  494. package/src/seed/validate.ts +15 -0
  495. package/src/seo/index.ts +2 -28
  496. package/src/seo/media-url.ts +32 -0
  497. package/src/settings/index.ts +32 -40
  498. package/src/taxonomies/index.ts +79 -12
  499. package/src/utils/isolate-cache.ts +189 -0
  500. package/src/virtual-modules.d.ts +11 -0
  501. package/src/widgets/index.ts +57 -54
  502. package/dist/api-Cs7DAACP.mjs.map +0 -1
  503. package/dist/apply-BWMV4Zmw.mjs.map +0 -1
  504. package/dist/byline-fields-BNy7Ng1U.d.mts.map +0 -1
  505. package/dist/content-CyqOmOzm.mjs.map +0 -1
  506. package/dist/cron-DZovZUnC.mjs.map +0 -1
  507. package/dist/index-CjKdMZ3U.d.mts.map +0 -1
  508. package/dist/loader-Dyx8dhFV.mjs.map +0 -1
  509. package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
  510. package/dist/menus-BKkxXCmd.mjs.map +0 -1
  511. package/dist/query-Ctlq1aOk.mjs.map +0 -1
  512. package/dist/redirects-C0L9JUk4.mjs.map +0 -1
  513. package/dist/runner-pt6Wl-l-.mjs.map +0 -1
  514. package/dist/seo-DfjLvu8i.mjs.map +0 -1
  515. package/dist/settings-b5zW1R1T.mjs +0 -235
  516. package/dist/settings-b5zW1R1T.mjs.map +0 -1
  517. package/dist/taxonomies-Crtzy4MT.mjs.map +0 -1
  518. package/dist/types-Cj2S6FuC.mjs +0 -3
  519. package/dist/types-D8bhH891.mjs.map +0 -1
  520. package/dist/types-DbCWhHet.d.mts.map +0 -1
  521. package/dist/types-i8_uzhMD.d.mts.map +0 -1
  522. package/dist/user-DzEUl5zA.mjs.map +0 -1
  523. package/dist/validate-Dy6nkNls.d.mts.map +0 -1
  524. package/dist/validate-JCXcsqiY.mjs.map +0 -1
  525. package/dist/version-CnS-Cr8A.mjs +0 -7
  526. package/dist/widgets-Bap1eS1X.mjs.map +0 -1
  527. package/src/plugins/scheduler/piggyback.ts +0 -71
  528. /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-Oq39ba-Z.mjs} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"seo-DfjLvu8i.mjs","names":[],"sources":["../src/api/handlers/seo.ts"],"sourcesContent":["/**\n * SEO Handlers\n *\n * Business logic for sitemap generation and robots.txt.\n */\n\nimport { sql, type Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport type { ApiResult } from \"../types.js\";\n\n/** Raw content data for sitemap generation — the route builds the actual URLs */\nexport interface SitemapContentEntry {\n\t/** Content ID (ULID) */\n\tid: string;\n\t/** Content slug, or null when the entry has no slug */\n\tslug: string | null;\n\t/** ISO date of last modification */\n\tupdatedAt: string;\n\t/**\n\t * Locale of this row (e.g. `\"en\"`, `\"fr\"`). Always present — rows in\n\t * pre-i18n databases are backfilled to the configured `defaultLocale`.\n\t */\n\tlocale: string;\n\t/**\n\t * `translation_group` ULID shared across all locale variants of the\n\t * same content. Used by the sitemap route to emit `hreflang`\n\t * alternates between siblings.\n\t */\n\ttranslationGroup: string | null;\n}\n\n/** Per-collection sitemap data with entries and URL pattern */\nexport interface SitemapCollectionData {\n\t/** Collection slug (e.g., \"post\", \"page\") */\n\tcollection: string;\n\t/** URL pattern with {slug} placeholder, or null for default /{collection}/{slug} */\n\turlPattern: string | null;\n\t/** Most recent updated_at across all entries (for sitemap index lastmod) */\n\tlastmod: string;\n\t/** Individual content entries */\n\tentries: SitemapContentEntry[];\n}\n\nexport interface SitemapDataResponse {\n\tcollections: SitemapCollectionData[];\n}\n\n/** Maximum entries per sitemap (per spec) */\nconst SITEMAP_MAX_ENTRIES = 50_000;\n\n/**\n * Collect all published, indexable content across SEO-enabled collections\n * for sitemap generation, grouped by collection.\n *\n * Only includes content from collections with `has_seo = 1`.\n * Excludes content with `seo_no_index = 1` in the `_emdash_seo` table.\n *\n * Returns raw data grouped per collection. The caller (route) is\n * responsible for building absolute URLs — this handler does NOT\n * assume a URL structure.\n */\nexport async function handleSitemapData(\n\tdb: Kysely<Database>,\n\t/** When set, only return data for this collection. */\n\tcollectionSlug?: string,\n): Promise<ApiResult<SitemapDataResponse>> {\n\ttry {\n\t\t// Find SEO-enabled collections (optionally filtered)\n\t\tlet query = db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\"slug\", \"url_pattern\"])\n\t\t\t.where(\"has_seo\", \"=\", 1);\n\n\t\tif (collectionSlug) {\n\t\t\tquery = query.where(\"slug\", \"=\", collectionSlug);\n\t\t}\n\n\t\tconst collections = await query.execute();\n\n\t\tconst result: SitemapCollectionData[] = [];\n\n\t\tfor (const col of collections) {\n\t\t\t// Validate the slug before using it as a table name identifier.\n\t\t\t// Should always pass (slugs are validated on creation), but\n\t\t\t// guards against corrupted DB data.\n\t\t\ttry {\n\t\t\t\tvalidateIdentifier(col.slug, \"collection slug\");\n\t\t\t} catch {\n\t\t\t\tconsole.warn(`[SITEMAP] Skipping collection with invalid slug: ${col.slug}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst tableName = `ec_${col.slug}`;\n\n\t\t\t// Query published, non-deleted content.\n\t\t\t// LEFT JOIN _emdash_seo to check noindex flag.\n\t\t\t// Content without an SEO row is assumed indexable (default).\n\t\t\t// Wrapped in try/catch so a missing/broken table doesn't fail the\n\t\t\t// entire sitemap — we skip that collection and continue.\n\t\t\ttry {\n\t\t\t\tconst rows = await sql<{\n\t\t\t\t\tslug: string | null;\n\t\t\t\t\tid: string;\n\t\t\t\t\tupdated_at: string;\n\t\t\t\t\tlocale: string;\n\t\t\t\t\ttranslation_group: string | null;\n\t\t\t\t}>`\n\t\t\t\t\tSELECT c.slug, c.id, c.updated_at, c.locale, c.translation_group\n\t\t\t\t\tFROM ${sql.ref(tableName)} c\n\t\t\t\t\tLEFT JOIN _emdash_seo s\n\t\t\t\t\t\tON s.collection = ${col.slug}\n\t\t\t\t\t\tAND s.content_id = c.id\n\t\t\t\t\tWHERE c.status = 'published'\n\t\t\t\t\tAND c.deleted_at IS NULL\n\t\t\t\t\tAND (s.seo_no_index IS NULL OR s.seo_no_index = 0)\n\t\t\t\t\tORDER BY c.updated_at DESC\n\t\t\t\t\tLIMIT ${SITEMAP_MAX_ENTRIES}\n\t\t\t\t`.execute(db);\n\n\t\t\t\tif (rows.rows.length === 0) continue;\n\n\t\t\t\tconst entries: SitemapContentEntry[] = [];\n\t\t\t\tfor (const row of rows.rows) {\n\t\t\t\t\tentries.push({\n\t\t\t\t\t\tid: row.id,\n\t\t\t\t\t\tslug: row.slug,\n\t\t\t\t\t\tupdatedAt: row.updated_at,\n\t\t\t\t\t\tlocale: row.locale,\n\t\t\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tresult.push({\n\t\t\t\t\tcollection: col.slug,\n\t\t\t\t\turlPattern: col.url_pattern,\n\t\t\t\t\t// Rows are ordered by updated_at DESC, so first row is the latest\n\t\t\t\t\tlastmod: rows.rows[0].updated_at,\n\t\t\t\t\tentries,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\t// Table missing or query error — skip this collection\n\t\t\t\tconsole.warn(`[SITEMAP] Failed to query collection \"${col.slug}\":`, err);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\treturn { success: true, data: { collections: result } };\n\t} catch (error) {\n\t\tconsole.error(\"[SITEMAP_ERROR]\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"SITEMAP_ERROR\", message: \"Failed to generate sitemap data\" },\n\t\t};\n\t}\n}\n"],"mappings":";;;;;;;;;;AAkDA,MAAM,sBAAsB;;;;;;;;;;;;AAa5B,eAAsB,kBACrB,IAEA,gBAC0C;AAC1C,KAAI;EAEH,IAAI,QAAQ,GACV,WAAW,sBAAsB,CACjC,OAAO,CAAC,QAAQ,cAAc,CAAC,CAC/B,MAAM,WAAW,KAAK,EAAE;AAE1B,MAAI,eACH,SAAQ,MAAM,MAAM,QAAQ,KAAK,eAAe;EAGjD,MAAM,cAAc,MAAM,MAAM,SAAS;EAEzC,MAAM,SAAkC,EAAE;AAE1C,OAAK,MAAM,OAAO,aAAa;AAI9B,OAAI;AACH,uBAAmB,IAAI,MAAM,kBAAkB;WACxC;AACP,YAAQ,KAAK,oDAAoD,IAAI,OAAO;AAC5E;;GAGD,MAAM,YAAY,MAAM,IAAI;AAO5B,OAAI;IACH,MAAM,OAAO,MAAM,GAMjB;;YAEM,IAAI,IAAI,UAAU,CAAC;;0BAEL,IAAI,KAAK;;;;;;aAMtB,oBAAoB;MAC3B,QAAQ,GAAG;AAEb,QAAI,KAAK,KAAK,WAAW,EAAG;IAE5B,MAAM,UAAiC,EAAE;AACzC,SAAK,MAAM,OAAO,KAAK,KACtB,SAAQ,KAAK;KACZ,IAAI,IAAI;KACR,MAAM,IAAI;KACV,WAAW,IAAI;KACf,QAAQ,IAAI;KACZ,kBAAkB,IAAI;KACtB,CAAC;AAGH,WAAO,KAAK;KACX,YAAY,IAAI;KAChB,YAAY,IAAI;KAEhB,SAAS,KAAK,KAAK,GAAG;KACtB;KACA,CAAC;YACM,KAAK;AAEb,YAAQ,KAAK,yCAAyC,IAAI,KAAK,KAAK,IAAI;AACxE;;;AAIF,SAAO;GAAE,SAAS;GAAM,MAAM,EAAE,aAAa,QAAQ;GAAE;UAC/C,OAAO;AACf,UAAQ,MAAM,mBAAmB,MAAM;AACvC,SAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAmC;GAC5E"}
@@ -1,235 +0,0 @@
1
- import { t as MediaRepository } from "./media-C-oovGCG.mjs";
2
- import { t as OptionsRepository } from "./options-BL4X94qY.mjs";
3
- import { n as peekRequestCache, r as requestCached } from "./request-cache-BYMs-BGX.mjs";
4
- import { r as getDb } from "./loader-Dyx8dhFV.mjs";
5
-
6
- //#region src/settings/index.ts
7
- /** Prefix for site settings in the options table */
8
- const SETTINGS_PREFIX = "site:";
9
- const SITE_SETTINGS_CACHE_KEY = Symbol.for("emdash:site-settings");
10
- const g = globalThis;
11
- const holder = g[SITE_SETTINGS_CACHE_KEY] ?? (() => {
12
- const h = {
13
- version: 0,
14
- cached: null,
15
- cachedVersion: -1
16
- };
17
- g[SITE_SETTINGS_CACHE_KEY] = h;
18
- return h;
19
- })();
20
- /**
21
- * Bump the isolate-wide site-settings cache version, forcing the next
22
- * `getSiteSettings()` to re-query the database.
23
- *
24
- * Called from every `site:*` write path. Other isolates still serve their
25
- * own cached copy until they expire — staleness bounded by isolate lifetime.
26
- */
27
- function invalidateSiteSettingsCache() {
28
- holder.version++;
29
- holder.cached = null;
30
- holder.cachedVersion = -1;
31
- }
32
- /**
33
- * Type guard for MediaReference values
34
- */
35
- function isMediaReference(value) {
36
- return typeof value === "object" && value !== null && "mediaId" in value;
37
- }
38
- /**
39
- * Resolve a media reference to include the full URL plus content metadata.
40
- *
41
- * Pulls `mimeType` and intrinsic dimensions from the media row so callers
42
- * can emit correct head tags (e.g. `<link rel="icon" type="image/svg+xml">`,
43
- * which Chromium requires when the URL has no `.svg` extension) without
44
- * a second round-trip to the media table.
45
- */
46
- async function resolveMediaReference(mediaRef, db, _storage) {
47
- if (!mediaRef?.mediaId) return mediaRef;
48
- try {
49
- const media = await new MediaRepository(db).findById(mediaRef.mediaId);
50
- if (media) return {
51
- ...mediaRef,
52
- url: `/_emdash/api/media/file/${media.storageKey}`,
53
- contentType: media.mimeType,
54
- ...media.width !== null ? { width: media.width } : {},
55
- ...media.height !== null ? { height: media.height } : {}
56
- };
57
- } catch {}
58
- return mediaRef;
59
- }
60
- /**
61
- * Get a single site setting by key
62
- *
63
- * Returns `undefined` if the setting has not been configured.
64
- * For media settings (logo, favicon), the URL is resolved automatically.
65
- *
66
- * @param key - The setting key (e.g., "title", "logo", "social")
67
- * @returns The setting value, or undefined if not set
68
- *
69
- * @example
70
- * ```ts
71
- * import { getSiteSetting } from "emdash";
72
- *
73
- * const title = await getSiteSetting("title");
74
- * const logo = await getSiteSetting("logo");
75
- * console.log(logo?.url); // Resolved URL
76
- * ```
77
- */
78
- async function getSiteSetting(key) {
79
- const primed = peekRequestCache("siteSettings");
80
- if (primed) return (await primed)[key];
81
- return requestCached(`siteSetting:${key}`, async () => {
82
- return getSiteSettingWithDb(key, await getDb());
83
- });
84
- }
85
- /**
86
- * Get a single site setting by key (with explicit db)
87
- *
88
- * @internal Use `getSiteSetting()` in templates. This variant is for admin routes
89
- * that already have a database handle.
90
- */
91
- async function getSiteSettingWithDb(key, db, storage = null) {
92
- const value = await new OptionsRepository(db).get(`${SETTINGS_PREFIX}${key}`);
93
- if (!value) return;
94
- if ((key === "logo" || key === "favicon") && isMediaReference(value)) return await resolveMediaReference(value, db, storage);
95
- if (key === "seo" && value && typeof value === "object") {
96
- const seo = value;
97
- if (seo.defaultOgImage) return {
98
- ...seo,
99
- defaultOgImage: await resolveMediaReference(seo.defaultOgImage, db, storage)
100
- };
101
- }
102
- return value;
103
- }
104
- /**
105
- * Get all site settings
106
- *
107
- * Returns all configured settings. Unset values are undefined.
108
- * Media references (logo/favicon) are resolved to include URLs.
109
- *
110
- * @example
111
- * ```ts
112
- * import { getSiteSettings } from "emdash";
113
- *
114
- * const settings = await getSiteSettings();
115
- * console.log(settings.title); // "My Site"
116
- * console.log(settings.logo?.url); // "/_emdash/api/media/file/abc123"
117
- * ```
118
- */
119
- function getSiteSettings() {
120
- return requestCached("siteSettings", () => {
121
- const versionAtCall = holder.version;
122
- if (holder.cached && holder.cachedVersion === versionAtCall) return holder.cached;
123
- const fetchPromise = (async () => {
124
- return getSiteSettingsWithDb(await getDb());
125
- })().catch((error) => {
126
- if (holder.cached === fetchPromise) {
127
- holder.cached = null;
128
- holder.cachedVersion = -1;
129
- }
130
- throw error;
131
- });
132
- holder.cached = fetchPromise;
133
- holder.cachedVersion = versionAtCall;
134
- return fetchPromise;
135
- });
136
- }
137
- /**
138
- * Get all site settings (with explicit db)
139
- *
140
- * @internal Use `getSiteSettings()` in templates. This variant is for admin routes
141
- * that already have a database handle.
142
- */
143
- async function getSiteSettingsWithDb(db, storage = null) {
144
- const allOptions = await new OptionsRepository(db).getByPrefix(SETTINGS_PREFIX);
145
- const settings = {};
146
- for (const [key, value] of allOptions) {
147
- const settingKey = key.replace(SETTINGS_PREFIX, "");
148
- settings[settingKey] = value;
149
- }
150
- const typedSettings = settings;
151
- if (typedSettings.logo) typedSettings.logo = await resolveMediaReference(typedSettings.logo, db, storage);
152
- if (typedSettings.favicon) typedSettings.favicon = await resolveMediaReference(typedSettings.favicon, db, storage);
153
- if (typedSettings.seo?.defaultOgImage) typedSettings.seo = {
154
- ...typedSettings.seo,
155
- defaultOgImage: await resolveMediaReference(typedSettings.seo.defaultOgImage, db, storage)
156
- };
157
- return typedSettings;
158
- }
159
- /**
160
- * Set site settings (internal function used by admin API)
161
- *
162
- * Merges provided settings with existing ones. Only provided fields are updated.
163
- * Media references should include just the mediaId; URLs are resolved on read.
164
- *
165
- * @param settings - Partial settings object with values to update
166
- * @param db - Kysely database instance
167
- * @returns Promise that resolves when settings are saved
168
- *
169
- * @internal
170
- *
171
- * @example
172
- * ```ts
173
- * // Update multiple settings at once
174
- * await setSiteSettings({
175
- * title: "My Site",
176
- * tagline: "Welcome",
177
- * logo: { mediaId: "med_123", alt: "Logo" }
178
- * }, db);
179
- * ```
180
- */
181
- async function setSiteSettings(settings, db) {
182
- const options = new OptionsRepository(db);
183
- const updates = {};
184
- for (const [key, value] of Object.entries(settings)) if (value !== void 0) updates[`${SETTINGS_PREFIX}${key}`] = value;
185
- try {
186
- await options.setMany(updates);
187
- } finally {
188
- invalidateSiteSettingsCache();
189
- }
190
- }
191
- /**
192
- * Get a single plugin setting by key.
193
- *
194
- * Plugin settings are stored in the options table under
195
- * `plugin:<pluginId>:settings:<key>`.
196
- */
197
- async function getPluginSetting(pluginId, key) {
198
- return getPluginSettingWithDb(pluginId, key, await getDb());
199
- }
200
- /**
201
- * Get a single plugin setting by key (with explicit db).
202
- *
203
- * @internal Use `getPluginSetting()` in templates and plugin rendering code.
204
- */
205
- async function getPluginSettingWithDb(pluginId, key, db) {
206
- return await new OptionsRepository(db).get(`plugin:${pluginId}:settings:${key}`) ?? void 0;
207
- }
208
- /**
209
- * Get all persisted plugin settings for a plugin.
210
- *
211
- * Defaults declared in `admin.settingsSchema` are not materialized
212
- * automatically; callers should apply their own fallback defaults.
213
- */
214
- async function getPluginSettings(pluginId) {
215
- return getPluginSettingsWithDb(pluginId, await getDb());
216
- }
217
- /**
218
- * Get all persisted plugin settings for a plugin (with explicit db).
219
- *
220
- * @internal Use `getPluginSettings()` in templates and plugin rendering code.
221
- */
222
- async function getPluginSettingsWithDb(pluginId, db) {
223
- const prefix = `plugin:${pluginId}:settings:`;
224
- const allOptions = await new OptionsRepository(db).getByPrefix(prefix);
225
- const settings = {};
226
- for (const [key, value] of allOptions) {
227
- if (!key.startsWith(prefix)) continue;
228
- settings[key.slice(prefix.length)] = value;
229
- }
230
- return settings;
231
- }
232
-
233
- //#endregion
234
- export { getSiteSettingsWithDb as a, getSiteSettings as i, getPluginSettings as n, invalidateSiteSettingsCache as o, getSiteSetting as r, setSiteSettings as s, getPluginSetting as t };
235
- //# sourceMappingURL=settings-b5zW1R1T.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"settings-b5zW1R1T.mjs","names":[],"sources":["../src/settings/index.ts"],"sourcesContent":["/**\n * Site Settings API\n *\n * Functions for getting and setting global site configuration.\n * Settings are stored in the options table with 'site:' prefix.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport type { Database } from \"../database/types.js\";\nimport { getDb } from \"../loader.js\";\nimport { peekRequestCache, requestCached } from \"../request-cache.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport type { SiteSettings, SiteSettingKey, MediaReference, SeoSettings } from \"./types.js\";\n\n/** Prefix for site settings in the options table */\nconst SETTINGS_PREFIX = \"site:\";\n\n/**\n * Worker-isolate cache for the resolved `site:*` settings.\n *\n * Site settings (title, logo, SEO defaults) change rarely but are read on\n * every public request. Caching across the isolate's lifetime drops the\n * `options WHERE name LIKE 'site:%'` prefix scan from once-per-request to\n * once-per-isolate. Cross-isolate staleness is bounded by isolate lifetime\n * (workerd typically recycles within minutes); acceptable for chrome.\n *\n * Stored on globalThis with a Symbol.for key so Vite SSR chunk duplication\n * doesn't produce two independent caches (same pattern as request-context.ts).\n *\n * Invalidation: every `site:*` write bumps `version`. Reads compare the\n * cached promise's version against the current version and refetch on\n * mismatch. Caching the promise (not the resolved value) lets concurrent\n * cold-isolate readers share the in-flight query.\n */\ninterface SiteSettingsHolder {\n\tversion: number;\n\tcached: Promise<Partial<SiteSettings>> | null;\n\tcachedVersion: number;\n}\n\nconst SITE_SETTINGS_CACHE_KEY = Symbol.for(\"emdash:site-settings\");\nconst g = globalThis as Record<symbol, unknown>;\nconst holder: SiteSettingsHolder =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(g[SITE_SETTINGS_CACHE_KEY] as SiteSettingsHolder | undefined) ??\n\t(() => {\n\t\tconst h: SiteSettingsHolder = { version: 0, cached: null, cachedVersion: -1 };\n\t\tg[SITE_SETTINGS_CACHE_KEY] = h;\n\t\treturn h;\n\t})();\n\n/**\n * Bump the isolate-wide site-settings cache version, forcing the next\n * `getSiteSettings()` to re-query the database.\n *\n * Called from every `site:*` write path. Other isolates still serve their\n * own cached copy until they expire — staleness bounded by isolate lifetime.\n */\nexport function invalidateSiteSettingsCache(): void {\n\tholder.version++;\n\tholder.cached = null;\n\tholder.cachedVersion = -1;\n}\n\n/**\n * Type guard for MediaReference values\n */\nfunction isMediaReference(value: unknown): value is MediaReference {\n\treturn typeof value === \"object\" && value !== null && \"mediaId\" in value;\n}\n\n/**\n * Resolve a media reference to include the full URL plus content metadata.\n *\n * Pulls `mimeType` and intrinsic dimensions from the media row so callers\n * can emit correct head tags (e.g. `<link rel=\"icon\" type=\"image/svg+xml\">`,\n * which Chromium requires when the URL has no `.svg` extension) without\n * a second round-trip to the media table.\n */\nasync function resolveMediaReference(\n\tmediaRef: MediaReference | undefined,\n\tdb: Kysely<Database>,\n\t_storage: Storage | null,\n): Promise<MediaReference | undefined> {\n\tif (!mediaRef?.mediaId) {\n\t\treturn mediaRef;\n\t}\n\n\ttry {\n\t\tconst mediaRepo = new MediaRepository(db);\n\t\tconst media = await mediaRepo.findById(mediaRef.mediaId);\n\n\t\tif (media) {\n\t\t\t// Construct URL using the same pattern as API handlers\n\t\t\treturn {\n\t\t\t\t...mediaRef,\n\t\t\t\turl: `/_emdash/api/media/file/${media.storageKey}`,\n\t\t\t\tcontentType: media.mimeType,\n\t\t\t\t...(media.width !== null ? { width: media.width } : {}),\n\t\t\t\t...(media.height !== null ? { height: media.height } : {}),\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// If media not found or error, return the reference as-is\n\t}\n\n\treturn mediaRef;\n}\n\n/**\n * Get a single site setting by key\n *\n * Returns `undefined` if the setting has not been configured.\n * For media settings (logo, favicon), the URL is resolved automatically.\n *\n * @param key - The setting key (e.g., \"title\", \"logo\", \"social\")\n * @returns The setting value, or undefined if not set\n *\n * @example\n * ```ts\n * import { getSiteSetting } from \"emdash\";\n *\n * const title = await getSiteSetting(\"title\");\n * const logo = await getSiteSetting(\"logo\");\n * console.log(logo?.url); // Resolved URL\n * ```\n */\nexport async function getSiteSetting<K extends SiteSettingKey>(\n\tkey: K,\n): Promise<SiteSettings[K] | undefined> {\n\t// If `getSiteSettings()` has already been called in this request,\n\t// read from that (request-cached) batch rather than firing a second\n\t// options-table query. Common layout: a Base template pulls the\n\t// whole settings object up-front, then `EmDashHead` or a plugin\n\t// asks for one key — no reason the singular call should round-trip\n\t// again.\n\tconst primed = peekRequestCache<Partial<SiteSettings>>(\"siteSettings\");\n\tif (primed) {\n\t\tconst settings = await primed;\n\t\treturn settings[key];\n\t}\n\n\t// Otherwise cache per-key. Templates that pull several settings\n\t// independently still share the in-flight query for each one.\n\treturn requestCached(`siteSetting:${key}`, async () => {\n\t\tconst db = await getDb();\n\t\treturn getSiteSettingWithDb(key, db);\n\t});\n}\n\n/**\n * Get a single site setting by key (with explicit db)\n *\n * @internal Use `getSiteSetting()` in templates. This variant is for admin routes\n * that already have a database handle.\n */\nexport async function getSiteSettingWithDb<K extends SiteSettingKey>(\n\tkey: K,\n\tdb: Kysely<Database>,\n\tstorage: Storage | null = null,\n): Promise<SiteSettings[K] | undefined> {\n\tconst options = new OptionsRepository(db);\n\tconst value = await options.get<SiteSettings[K]>(`${SETTINGS_PREFIX}${key}`);\n\n\tif (!value) {\n\t\treturn undefined;\n\t}\n\n\t// Resolve media references if needed.\n\t// TS cannot narrow generic K from key equality checks — this is a known limitation.\n\t// We use the non-generic getSiteSettingsWithDb for media resolution instead.\n\tif ((key === \"logo\" || key === \"favicon\") && isMediaReference(value)) {\n\t\tconst resolved = await resolveMediaReference(value, db, storage);\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- TS can't narrow generic K from key equality; resolved type is correct\n\t\treturn resolved as SiteSettings[K] | undefined;\n\t}\n\n\tif (key === \"seo\" && value && typeof value === \"object\") {\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- TS can't narrow generic K from key equality\n\t\tconst seo = value as SeoSettings;\n\t\tif (seo.defaultOgImage) {\n\t\t\tconst resolved = {\n\t\t\t\t...seo,\n\t\t\t\tdefaultOgImage: await resolveMediaReference(seo.defaultOgImage, db, storage),\n\t\t\t};\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- TS can't narrow generic K from key equality\n\t\t\treturn resolved as SiteSettings[K] | undefined;\n\t\t}\n\t}\n\n\treturn value;\n}\n\n/**\n * Get all site settings\n *\n * Returns all configured settings. Unset values are undefined.\n * Media references (logo/favicon) are resolved to include URLs.\n *\n * @example\n * ```ts\n * import { getSiteSettings } from \"emdash\";\n *\n * const settings = await getSiteSettings();\n * console.log(settings.title); // \"My Site\"\n * console.log(settings.logo?.url); // \"/_emdash/api/media/file/abc123\"\n * ```\n */\nexport function getSiteSettings(): Promise<Partial<SiteSettings>> {\n\treturn requestCached(\"siteSettings\", () => {\n\t\tconst versionAtCall = holder.version;\n\t\tif (holder.cached && holder.cachedVersion === versionAtCall) {\n\t\t\treturn holder.cached;\n\t\t}\n\t\tconst fetchPromise = (async () => {\n\t\t\tconst db = await getDb();\n\t\t\treturn getSiteSettingsWithDb(db);\n\t\t})().catch((error) => {\n\t\t\tif (holder.cached === fetchPromise) {\n\t\t\t\tholder.cached = null;\n\t\t\t\tholder.cachedVersion = -1;\n\t\t\t}\n\t\t\tthrow error;\n\t\t});\n\t\tholder.cached = fetchPromise;\n\t\tholder.cachedVersion = versionAtCall;\n\t\treturn fetchPromise;\n\t});\n}\n\n/**\n * Get all site settings (with explicit db)\n *\n * @internal Use `getSiteSettings()` in templates. This variant is for admin routes\n * that already have a database handle.\n */\nexport async function getSiteSettingsWithDb(\n\tdb: Kysely<Database>,\n\tstorage: Storage | null = null,\n): Promise<Partial<SiteSettings>> {\n\tconst options = new OptionsRepository(db);\n\tconst allOptions = await options.getByPrefix(SETTINGS_PREFIX);\n\n\tconst settings: Record<string, unknown> = {};\n\n\t// Convert Map to settings object, removing the prefix\n\tfor (const [key, value] of allOptions) {\n\t\tconst settingKey = key.replace(SETTINGS_PREFIX, \"\");\n\t\tsettings[settingKey] = value;\n\t}\n\n\tconst typedSettings = settings as Partial<SiteSettings>;\n\n\t// Resolve media references\n\tif (typedSettings.logo) {\n\t\ttypedSettings.logo = await resolveMediaReference(typedSettings.logo, db, storage);\n\t}\n\tif (typedSettings.favicon) {\n\t\ttypedSettings.favicon = await resolveMediaReference(typedSettings.favicon, db, storage);\n\t}\n\tif (typedSettings.seo?.defaultOgImage) {\n\t\ttypedSettings.seo = {\n\t\t\t...typedSettings.seo,\n\t\t\tdefaultOgImage: await resolveMediaReference(typedSettings.seo.defaultOgImage, db, storage),\n\t\t};\n\t}\n\n\treturn typedSettings;\n}\n\n/**\n * Set site settings (internal function used by admin API)\n *\n * Merges provided settings with existing ones. Only provided fields are updated.\n * Media references should include just the mediaId; URLs are resolved on read.\n *\n * @param settings - Partial settings object with values to update\n * @param db - Kysely database instance\n * @returns Promise that resolves when settings are saved\n *\n * @internal\n *\n * @example\n * ```ts\n * // Update multiple settings at once\n * await setSiteSettings({\n * title: \"My Site\",\n * tagline: \"Welcome\",\n * logo: { mediaId: \"med_123\", alt: \"Logo\" }\n * }, db);\n * ```\n */\nexport async function setSiteSettings(\n\tsettings: Partial<SiteSettings>,\n\tdb: Kysely<Database>,\n): Promise<void> {\n\tconst options = new OptionsRepository(db);\n\n\t// Convert settings to options format\n\tconst updates: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(settings)) {\n\t\tif (value !== undefined) {\n\t\t\tupdates[`${SETTINGS_PREFIX}${key}`] = value;\n\t\t}\n\t}\n\n\ttry {\n\t\tawait options.setMany(updates);\n\t} finally {\n\t\tinvalidateSiteSettingsCache();\n\t}\n}\n\n/**\n * Get a single plugin setting by key.\n *\n * Plugin settings are stored in the options table under\n * `plugin:<pluginId>:settings:<key>`.\n */\nexport async function getPluginSetting<T = unknown>(\n\tpluginId: string,\n\tkey: string,\n): Promise<T | undefined> {\n\tconst db = await getDb();\n\treturn getPluginSettingWithDb<T>(pluginId, key, db);\n}\n\n/**\n * Get a single plugin setting by key (with explicit db).\n *\n * @internal Use `getPluginSetting()` in templates and plugin rendering code.\n */\nexport async function getPluginSettingWithDb<T = unknown>(\n\tpluginId: string,\n\tkey: string,\n\tdb: Kysely<Database>,\n): Promise<T | undefined> {\n\tconst options = new OptionsRepository(db);\n\tconst value = await options.get<T>(`plugin:${pluginId}:settings:${key}`);\n\treturn value ?? undefined;\n}\n\n/**\n * Get all persisted plugin settings for a plugin.\n *\n * Defaults declared in `admin.settingsSchema` are not materialized\n * automatically; callers should apply their own fallback defaults.\n */\nexport async function getPluginSettings(pluginId: string): Promise<Record<string, unknown>> {\n\tconst db = await getDb();\n\treturn getPluginSettingsWithDb(pluginId, db);\n}\n\n/**\n * Get all persisted plugin settings for a plugin (with explicit db).\n *\n * @internal Use `getPluginSettings()` in templates and plugin rendering code.\n */\nexport async function getPluginSettingsWithDb(\n\tpluginId: string,\n\tdb: Kysely<Database>,\n): Promise<Record<string, unknown>> {\n\tconst prefix = `plugin:${pluginId}:settings:`;\n\tconst options = new OptionsRepository(db);\n\tconst allOptions = await options.getByPrefix(prefix);\n\n\tconst settings: Record<string, unknown> = {};\n\tfor (const [key, value] of allOptions) {\n\t\tif (!key.startsWith(prefix)) {\n\t\t\tcontinue;\n\t\t}\n\t\tsettings[key.slice(prefix.length)] = value;\n\t}\n\n\treturn settings;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"taxonomies-Crtzy4MT.mjs","names":[],"sources":["../src/taxonomies/index.ts"],"sourcesContent":["/**\n * Runtime API for taxonomies.\n *\n * All helpers are locale-aware. When a locale is not passed explicitly we fall\n * back to the request context or the configured `defaultLocale` (see\n * `i18n/resolve.ts`).\n *\n * Because `content_taxonomies.taxonomy_id` stores the translation_group (not a\n * specific term id), the joins here are `taxonomies.translation_group =\n * content_taxonomies.taxonomy_id` + filter by `taxonomies.locale`, which picks\n * the right per-locale term.\n */\n\nimport { resolveLocale, resolveLocaleChain } from \"../i18n/resolve.js\";\nimport { getDb } from \"../loader.js\";\nimport { peekRequestCache, requestCached, setRequestCacheEntry } from \"../request-cache.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.js\";\nimport { isMissingTableError } from \"../utils/db-errors.js\";\nimport type { TaxonomyDef, TaxonomyTerm, TaxonomyTermRow } from \"./types.js\";\n\nexport interface TaxonomyQueryOptions {\n\tlocale?: string;\n}\n\n/**\n * No-op — kept for API compatibility.\n */\nexport function invalidateTermCache(): void {\n\t// Intentionally empty.\n}\n\n/**\n * Get every taxonomy definition. Definitions are per-locale (one row per\n * locale inside the same translation_group) — by default we resolve to the\n * active locale.\n */\nexport async function getTaxonomyDefs(options: TaxonomyQueryOptions = {}): Promise<TaxonomyDef[]> {\n\tconst locale = resolveLocale(options.locale);\n\treturn requestCached(`taxonomy-defs:${locale ?? \"*\"}`, async () => {\n\t\tconst db = await getDb();\n\t\tlet query = db.selectFrom(\"_emdash_taxonomy_defs\").selectAll();\n\t\tif (locale !== undefined) query = query.where(\"locale\", \"=\", locale);\n\t\tconst rows = await query.execute();\n\t\treturn rows.map(rowToTaxonomyDef);\n\t});\n}\n\n/**\n * Get a single taxonomy definition by name. Uses the fallback chain so even\n * if there is no translation for the active locale we still return something.\n *\n * If `getTaxonomyDefs()` has already loaded the full list in this request\n * (which happens during entry-term hydration on every page that renders a\n * collection), search the matching def in memory rather than running a\n * second query against `_emdash_taxonomy_defs`.\n */\nexport async function getTaxonomyDef(\n\tname: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyDef | null> {\n\tconst chain = resolveLocaleChain(options.locale);\n\tconst peekKey = `taxonomy-defs:${resolveLocale(options.locale) ?? \"*\"}`;\n\tconst allDefs = peekRequestCache<TaxonomyDef[]>(peekKey);\n\tif (allDefs) {\n\t\tconst defs = await allDefs;\n\t\tif (chain.length === 0) return defs.find((d) => d.name === name) ?? null;\n\t\tfor (const locale of chain) {\n\t\t\tconst found = defs.find((d) => d.name === name && d.locale === locale);\n\t\t\tif (found) return found;\n\t\t}\n\t\treturn null;\n\t}\n\n\treturn requestCached(`taxonomy-def:${name}:${chain.join(\",\")}`, async () => {\n\t\tconst db = await getDb();\n\n\t\tif (chain.length === 0) {\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t\t.executeTakeFirst();\n\t\t\treturn row ? rowToTaxonomyDef(row) : null;\n\t\t}\n\n\t\tfor (const locale of chain) {\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"name\", \"=\", name)\n\t\t\t\t.where(\"locale\", \"=\", locale)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (row) return rowToTaxonomyDef(row);\n\t\t}\n\t\treturn null;\n\t});\n}\n\n/**\n * All terms of a taxonomy in a specific locale (flat for non-hierarchical,\n * tree for hierarchical).\n */\nexport async function getTaxonomyTerms(\n\ttaxonomyName: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyTerm[]> {\n\tconst locale = resolveLocale(options.locale);\n\treturn requestCached(`taxonomy-terms:${taxonomyName}:${locale ?? \"*\"}`, async () => {\n\t\tconst db = await getDb();\n\n\t\tconst def = await getTaxonomyDef(taxonomyName, options);\n\t\tif (!def) return [];\n\n\t\tlet termsQuery = db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.selectAll()\n\t\t\t.where(\"name\", \"=\", taxonomyName)\n\t\t\t.orderBy(\"label\", \"asc\");\n\t\tif (locale !== undefined) termsQuery = termsQuery.where(\"locale\", \"=\", locale);\n\t\tconst rows = await termsQuery.execute();\n\n\t\t// Counts are keyed by translation_group (what the pivot stores).\n\t\tconst countsResult = await db\n\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t.select([\"taxonomy_id\"])\n\t\t\t.select((eb) => eb.fn.count<number>(\"entry_id\").as(\"count\"))\n\t\t\t.groupBy(\"taxonomy_id\")\n\t\t\t.execute();\n\t\tconst counts = new Map<string, number>();\n\t\tfor (const row of countsResult) counts.set(row.taxonomy_id, row.count);\n\n\t\tconst flatTerms: TaxonomyTermRow[] = rows.map((row) => ({\n\t\t\tid: row.id,\n\t\t\tname: row.name,\n\t\t\tslug: row.slug,\n\t\t\tlabel: row.label,\n\t\t\tparent_id: row.parent_id,\n\t\t\tdata: row.data,\n\t\t\tlocale: row.locale,\n\t\t\ttranslation_group: row.translation_group,\n\t\t}));\n\n\t\tif (def.hierarchical) return buildTree(flatTerms, counts);\n\n\t\treturn flatTerms.map((term) => ({\n\t\t\tid: term.id,\n\t\t\tname: term.name,\n\t\t\tslug: term.slug,\n\t\t\tlabel: term.label,\n\t\t\tchildren: [],\n\t\t\tcount: counts.get(term.translation_group ?? term.id) ?? 0,\n\t\t\tlocale: term.locale,\n\t\t\ttranslationGroup: term.translation_group,\n\t\t}));\n\t});\n}\n\n/**\n * Get a single term by (taxonomy, slug). Honours the fallback chain — if the\n * slug exists in a fallback locale, we return that row (useful for deep-linking\n * to a term page when the translation is missing).\n */\nexport async function getTerm(\n\ttaxonomyName: string,\n\tslug: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyTerm | null> {\n\tconst db = await getDb();\n\tconst chain = resolveLocaleChain(options.locale);\n\n\tlet row: Awaited<ReturnType<ReturnType<typeof selectTerm>[\"executeTakeFirst\"]>>;\n\tconst selectTerm = () =>\n\t\tdb\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.selectAll()\n\t\t\t.where(\"name\", \"=\", taxonomyName)\n\t\t\t.where(\"slug\", \"=\", slug);\n\n\tif (chain.length === 0) {\n\t\trow = await selectTerm().orderBy(\"locale\", \"asc\").executeTakeFirst();\n\t} else {\n\t\trow = undefined;\n\t\tfor (const locale of chain) {\n\t\t\trow = await selectTerm().where(\"locale\", \"=\", locale).executeTakeFirst();\n\t\t\tif (row) break;\n\t\t}\n\t}\n\n\tif (!row) return null;\n\n\tlet childrenQuery = db\n\t\t.selectFrom(\"taxonomies\")\n\t\t.selectAll()\n\t\t.where(\"parent_id\", \"=\", row.id)\n\t\t.orderBy(\"label\", \"asc\");\n\tconst termLocale = row.locale;\n\tif (termLocale) childrenQuery = childrenQuery.where(\"locale\", \"=\", termLocale);\n\n\t// The usage-count and children queries both depend only on the term row,\n\t// so run them concurrently to save a round trip on remote databases.\n\tconst [countResult, childRows] = await Promise.all([\n\t\tdb\n\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t.select((eb) => eb.fn.count<number>(\"entry_id\").as(\"count\"))\n\t\t\t.where(\"taxonomy_id\", \"=\", row.translation_group ?? row.id)\n\t\t\t.executeTakeFirst(),\n\t\tchildrenQuery.execute(),\n\t]);\n\tconst count = countResult?.count ?? 0;\n\n\tconst children = childRows.map<TaxonomyTerm>((child) => ({\n\t\tid: child.id,\n\t\tname: child.name,\n\t\tslug: child.slug,\n\t\tlabel: child.label,\n\t\tparentId: child.parent_id ?? undefined,\n\t\tchildren: [],\n\t\tlocale: child.locale,\n\t\ttranslationGroup: child.translation_group,\n\t}));\n\n\treturn {\n\t\tid: row.id,\n\t\tname: row.name,\n\t\tslug: row.slug,\n\t\tlabel: row.label,\n\t\tparentId: row.parent_id ?? undefined,\n\t\tdescription: row.data ? JSON.parse(row.data).description : undefined,\n\t\tchildren,\n\t\tcount,\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t};\n}\n\n/**\n * Terms assigned to a content entry, resolved into the active locale. Terms\n * whose translation_group lacks a row in the requested locale are omitted.\n */\nexport function getEntryTerms(\n\tcollection: string,\n\tentryId: string,\n\ttaxonomyName?: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<TaxonomyTerm[]> {\n\tconst locale = resolveLocale(options.locale);\n\treturn requestCached(\n\t\t`terms:${collection}:${entryId}:${taxonomyName ?? \"*\"}:${locale ?? \"*\"}`,\n\t\tasync () => {\n\t\t\tconst db = await getDb();\n\n\t\t\tlet query = db\n\t\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t\t.innerJoin(\"taxonomies\", \"taxonomies.translation_group\", \"content_taxonomies.taxonomy_id\")\n\t\t\t\t.selectAll(\"taxonomies\")\n\t\t\t\t.where(\"content_taxonomies.collection\", \"=\", collection)\n\t\t\t\t.where(\"content_taxonomies.entry_id\", \"=\", entryId);\n\n\t\t\tif (taxonomyName) query = query.where(\"taxonomies.name\", \"=\", taxonomyName);\n\t\t\tif (locale !== undefined) query = query.where(\"taxonomies.locale\", \"=\", locale);\n\n\t\t\tconst rows = await query.execute();\n\t\t\treturn rows.map<TaxonomyTerm>((row) => ({\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tslug: row.slug,\n\t\t\t\tlabel: row.label,\n\t\t\t\tparentId: row.parent_id ?? undefined,\n\t\t\t\tchildren: [],\n\t\t\t\tlocale: row.locale,\n\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t}));\n\t\t},\n\t);\n}\n\n/**\n * Terms for multiple entries of one taxonomy, single query.\n */\nexport async function getTermsForEntries(\n\tcollection: string,\n\tentryIds: string[],\n\ttaxonomyName: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<Map<string, TaxonomyTerm[]>> {\n\tconst result = new Map<string, TaxonomyTerm[]>();\n\tconst uniqueIds = [...new Set(entryIds)];\n\tfor (const id of uniqueIds) result.set(id, []);\n\tif (uniqueIds.length === 0) return result;\n\n\tconst db = await getDb();\n\tconst locale = resolveLocale(options.locale);\n\n\tfor (const chunk of chunks(uniqueIds, SQL_BATCH_SIZE)) {\n\t\tlet rows;\n\t\ttry {\n\t\t\tlet query = db\n\t\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t\t.innerJoin(\"taxonomies\", \"taxonomies.translation_group\", \"content_taxonomies.taxonomy_id\")\n\t\t\t\t.select([\n\t\t\t\t\t\"content_taxonomies.entry_id\",\n\t\t\t\t\t\"taxonomies.id\",\n\t\t\t\t\t\"taxonomies.name\",\n\t\t\t\t\t\"taxonomies.slug\",\n\t\t\t\t\t\"taxonomies.label\",\n\t\t\t\t\t\"taxonomies.parent_id\",\n\t\t\t\t\t\"taxonomies.locale\",\n\t\t\t\t\t\"taxonomies.translation_group\",\n\t\t\t\t])\n\t\t\t\t.where(\"content_taxonomies.collection\", \"=\", collection)\n\t\t\t\t.where(\"content_taxonomies.entry_id\", \"in\", chunk)\n\t\t\t\t.where(\"taxonomies.name\", \"=\", taxonomyName);\n\t\t\tif (locale !== undefined) query = query.where(\"taxonomies.locale\", \"=\", locale);\n\t\t\trows = await query.execute();\n\t\t} catch (error) {\n\t\t\tif (isMissingTableError(error)) return result;\n\t\t\tthrow error;\n\t\t}\n\n\t\tfor (const row of rows) {\n\t\t\tconst term: TaxonomyTerm = {\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tslug: row.slug,\n\t\t\t\tlabel: row.label,\n\t\t\t\tparentId: row.parent_id ?? undefined,\n\t\t\t\tchildren: [],\n\t\t\t\tlocale: row.locale,\n\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t};\n\t\t\tconst terms = result.get(row.entry_id);\n\t\t\tif (terms) terms.push(term);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Batch-fetch terms for multiple entries across ALL taxonomies in one query.\n * Primes the request-cache for subsequent per-entry calls to `getEntryTerms`.\n */\nexport async function getAllTermsForEntries(\n\tcollection: string,\n\tentryIds: string[],\n\toptions: TaxonomyQueryOptions = {},\n): Promise<Map<string, Record<string, TaxonomyTerm[]>>> {\n\tconst result = new Map<string, Record<string, TaxonomyTerm[]>>();\n\tconst uniqueIds = [...new Set(entryIds)];\n\tfor (const id of uniqueIds) result.set(id, {});\n\tif (uniqueIds.length === 0) return result;\n\n\tconst db = await getDb();\n\tconst locale = resolveLocale(options.locale);\n\tconst applicableTaxonomyNames = await getCollectionTaxonomyNames(collection, { locale });\n\n\tfor (const chunk of chunks(uniqueIds, SQL_BATCH_SIZE)) {\n\t\tlet rows;\n\t\ttry {\n\t\t\tlet query = db\n\t\t\t\t.selectFrom(\"content_taxonomies\")\n\t\t\t\t.innerJoin(\"taxonomies\", \"taxonomies.translation_group\", \"content_taxonomies.taxonomy_id\")\n\t\t\t\t.select([\n\t\t\t\t\t\"content_taxonomies.entry_id\",\n\t\t\t\t\t\"taxonomies.id\",\n\t\t\t\t\t\"taxonomies.name\",\n\t\t\t\t\t\"taxonomies.slug\",\n\t\t\t\t\t\"taxonomies.label\",\n\t\t\t\t\t\"taxonomies.parent_id\",\n\t\t\t\t\t\"taxonomies.locale\",\n\t\t\t\t\t\"taxonomies.translation_group\",\n\t\t\t\t])\n\t\t\t\t.where(\"content_taxonomies.collection\", \"=\", collection)\n\t\t\t\t.where(\"content_taxonomies.entry_id\", \"in\", chunk)\n\t\t\t\t.orderBy(\"taxonomies.label\", \"asc\");\n\t\t\tif (locale !== undefined) query = query.where(\"taxonomies.locale\", \"=\", locale);\n\t\t\trows = await query.execute();\n\t\t} catch (error) {\n\t\t\tif (isMissingTableError(error)) {\n\t\t\t\tfor (const id of uniqueIds) {\n\t\t\t\t\tprimeEntryTermsCache(collection, id, {}, applicableTaxonomyNames, locale);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tfor (const row of rows) {\n\t\t\tconst term: TaxonomyTerm = {\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tslug: row.slug,\n\t\t\t\tlabel: row.label,\n\t\t\t\tparentId: row.parent_id ?? undefined,\n\t\t\t\tchildren: [],\n\t\t\t\tlocale: row.locale,\n\t\t\t\ttranslationGroup: row.translation_group,\n\t\t\t};\n\t\t\tconst byTaxonomy = result.get(row.entry_id);\n\t\t\tif (!byTaxonomy) continue;\n\t\t\tconst existing = byTaxonomy[row.name];\n\t\t\tif (existing) existing.push(term);\n\t\t\telse byTaxonomy[row.name] = [term];\n\t\t}\n\t}\n\n\tfor (const [entryId, byTaxonomy] of result) {\n\t\tprimeEntryTermsCache(collection, entryId, byTaxonomy, applicableTaxonomyNames, locale);\n\t}\n\n\treturn result;\n}\n\n/**\n * Return the list of taxonomy names applicable to a collection, request-\n * cached so a page render only pays for it once.\n *\n * Returns an empty list when taxonomies haven't been defined yet.\n */\nasync function getCollectionTaxonomyNames(\n\tcollection: string,\n\toptions: TaxonomyQueryOptions,\n): Promise<string[]> {\n\ttry {\n\t\tconst defs = await getTaxonomyDefs(options);\n\t\treturn defs.filter((d) => d.collections.includes(collection)).map((d) => d.name);\n\t} catch (error) {\n\t\tif (isMissingTableError(error)) return [];\n\t\tthrow error;\n\t}\n}\n\n/**\n * Pre-populate the request-cache for every getEntryTerms call-shape that\n * could hit this entry:\n *\n * getEntryTerms(collection, entryId) -> key `terms:C:E:*`\n * getEntryTerms(collection, entryId, \"tag\") -> key `terms:C:E:tag`\n * getEntryTerms(collection, entryId, \"category\") -> key `terms:C:E:category`\n * ...one per taxonomy that applies to this collection\n *\n * Taxonomies with no rows on this entry are seeded with `[]` so legacy\n * callers short-circuit to the cached empty array instead of re-querying.\n */\nfunction primeEntryTermsCache(\n\tcollection: string,\n\tentryId: string,\n\tbyTaxonomy: Record<string, TaxonomyTerm[]>,\n\tapplicableTaxonomyNames: string[],\n\tlocale: string | undefined,\n): void {\n\tconst localeKey = locale ?? \"*\";\n\tfor (const name of applicableTaxonomyNames) {\n\t\tsetRequestCacheEntry(\n\t\t\t`terms:${collection}:${entryId}:${name}:${localeKey}`,\n\t\t\tbyTaxonomy[name] ?? [],\n\t\t);\n\t}\n\tfor (const [name, terms] of Object.entries(byTaxonomy)) {\n\t\tsetRequestCacheEntry(`terms:${collection}:${entryId}:${name}:${localeKey}`, terms);\n\t}\n\tconst allTerms = Object.values(byTaxonomy).flat();\n\tsetRequestCacheEntry(`terms:${collection}:${entryId}:*:${localeKey}`, allTerms);\n}\n\n/**\n * Get entries by term. Both the lookup (term slug in the active locale) and\n * the content query respect the active locale.\n */\nexport async function getEntriesByTerm(\n\tcollection: string,\n\ttaxonomyName: string,\n\ttermSlug: string,\n\toptions: TaxonomyQueryOptions = {},\n): Promise<Array<{ id: string; data: Record<string, unknown> }>> {\n\tconst { getEmDashCollection } = await import(\"../query.js\");\n\n\tconst queryOptions: Record<string, unknown> = {\n\t\twhere: { [taxonomyName]: termSlug },\n\t};\n\tif (options.locale !== undefined) queryOptions.locale = options.locale;\n\tconst { entries } = await getEmDashCollection(collection, queryOptions);\n\treturn entries;\n}\n\nfunction rowToTaxonomyDef(row: {\n\tid: string;\n\tname: string;\n\tlabel: string;\n\tlabel_singular: string | null;\n\thierarchical: number;\n\tcollections: string | null;\n\tlocale: string;\n\ttranslation_group: string | null;\n}): TaxonomyDef {\n\treturn {\n\t\tid: row.id,\n\t\tname: row.name,\n\t\tlabel: row.label,\n\t\tlabelSingular: row.label_singular ?? undefined,\n\t\thierarchical: row.hierarchical === 1,\n\t\tcollections: row.collections ? JSON.parse(row.collections) : [],\n\t\tlocale: row.locale,\n\t\ttranslationGroup: row.translation_group,\n\t};\n}\n\n/**\n * Build tree structure from flat terms\n */\nfunction buildTree(flatTerms: TaxonomyTermRow[], counts: Map<string, number>): TaxonomyTerm[] {\n\tconst map = new Map<string, TaxonomyTerm>();\n\tconst roots: TaxonomyTerm[] = [];\n\n\tfor (const term of flatTerms) {\n\t\tmap.set(term.id, {\n\t\t\tid: term.id,\n\t\t\tname: term.name,\n\t\t\tslug: term.slug,\n\t\t\tlabel: term.label,\n\t\t\tparentId: term.parent_id ?? undefined,\n\t\t\tdescription: term.data ? JSON.parse(term.data).description : undefined,\n\t\t\tchildren: [],\n\t\t\tcount: counts.get(term.translation_group ?? term.id) ?? 0,\n\t\t\tlocale: term.locale,\n\t\t\ttranslationGroup: term.translation_group,\n\t\t});\n\t}\n\n\tfor (const term of map.values()) {\n\t\tif (term.parentId && map.has(term.parentId)) {\n\t\t\tmap.get(term.parentId)!.children.push(term);\n\t\t} else {\n\t\t\troots.push(term);\n\t\t}\n\t}\n\n\treturn roots;\n}\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { CAPABILITY_RENAMES, isDeprecatedCapability, normalizeCapabilities, normalizeCapability } from "@emdash-cms/plugin-types";
2
-
3
- export { normalizeCapability as i, isDeprecatedCapability as n, normalizeCapabilities as r, CAPABILITY_RENAMES as t };
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-D8bhH891.mjs","names":[],"sources":["../src/schema/types.ts"],"sourcesContent":["/**\n * Schema Registry Types\n *\n * These types represent the schema definitions stored in D1.\n * They are the source of truth for all collections and fields.\n */\n\n/**\n * Supported field types\n */\nexport type FieldType =\n\t| \"string\"\n\t| \"text\"\n\t| \"url\"\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\n/**\n * Array of all field types for validation\n */\nexport const FIELD_TYPES: readonly FieldType[] = [\n\t\"string\",\n\t\"text\",\n\t\"url\",\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\n/**\n * SQLite column types that map from field types\n */\nexport type ColumnType = \"TEXT\" | \"REAL\" | \"INTEGER\" | \"JSON\";\n\n/**\n * Map field types to their SQLite column types\n */\nexport const FIELD_TYPE_TO_COLUMN: Record<FieldType, ColumnType> = {\n\tstring: \"TEXT\",\n\ttext: \"TEXT\",\n\tnumber: \"REAL\",\n\tinteger: \"INTEGER\",\n\tboolean: \"INTEGER\",\n\tdatetime: \"TEXT\",\n\tselect: \"TEXT\",\n\tmultiSelect: \"JSON\",\n\tportableText: \"JSON\",\n\timage: \"TEXT\",\n\tfile: \"TEXT\",\n\treference: \"TEXT\",\n\tjson: \"JSON\",\n\tslug: \"TEXT\",\n\turl: \"TEXT\",\n\trepeater: \"JSON\",\n};\n\n/**\n * Features a collection can support\n */\nexport type CollectionSupport =\n\t| \"drafts\"\n\t| \"revisions\"\n\t| \"preview\"\n\t| \"scheduling\"\n\t| \"search\"\n\t| \"seo\";\n\n/**\n * Sources for how a collection was created\n */\nexport type CollectionSource =\n\t| `template:${string}`\n\t| `import:${string}`\n\t| \"manual\"\n\t| \"discovered\"\n\t| \"seed\";\n\n/**\n * Validation rules for a field\n */\n/** Sub-field definition for repeater fields */\nexport interface RepeaterSubField {\n\tslug: string;\n\ttype: \"string\" | \"text\" | \"url\" | \"number\" | \"integer\" | \"boolean\" | \"datetime\" | \"select\";\n\tlabel: string;\n\trequired?: boolean;\n\toptions?: string[]; // For select sub-fields\n}\n\n/** Allowed types for repeater sub-fields (no nesting, no complex types) */\nexport const REPEATER_SUB_FIELD_TYPES = [\n\t\"string\",\n\t\"text\",\n\t\"url\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n] as const;\n\nexport interface FieldValidation {\n\trequired?: boolean;\n\tmin?: number;\n\tmax?: number;\n\tminLength?: number;\n\tmaxLength?: number;\n\tpattern?: string;\n\toptions?: string[]; // For select/multiSelect\n\tsubFields?: RepeaterSubField[]; // For repeater fields\n\tminItems?: number; // For repeater fields\n\tmaxItems?: number; // For repeater fields\n\tallowedMimeTypes?: string[];\n}\n\n/**\n * Widget options for field rendering\n */\nexport interface FieldWidgetOptions {\n\trows?: number; // For textarea\n\tshowPreview?: boolean; // For image/file\n\tcollection?: string; // For reference - which collection to reference\n\tallowMultiple?: boolean; // For reference\n\t[key: string]: unknown;\n}\n\n/**\n * A collection definition\n */\nexport interface Collection {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\tlabelSingular?: string;\n\tdescription?: string;\n\ticon?: string;\n\tsupports: CollectionSupport[];\n\tsource?: CollectionSource;\n\t/** Whether this collection has SEO metadata fields enabled */\n\thasSeo: boolean;\n\t/** URL pattern with {slug} placeholder (e.g. \"/{slug}\", \"/blog/{slug}\") */\n\turlPattern?: string;\n\t/** Whether comments are enabled for this collection */\n\tcommentsEnabled: boolean;\n\t/** Moderation strategy: \"all\" | \"first_time\" | \"none\" */\n\tcommentsModeration: \"all\" | \"first_time\" | \"none\";\n\t/** Auto-close comments after N days. 0 = never close. */\n\tcommentsClosedAfterDays: number;\n\t/** Auto-approve comments from authenticated CMS users */\n\tcommentsAutoApproveUsers: boolean;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/**\n * A field definition\n */\nexport interface Field {\n\tid: string;\n\tcollectionId: string;\n\tslug: string;\n\tlabel: string;\n\ttype: FieldType;\n\tcolumnType: ColumnType;\n\trequired: boolean;\n\tunique: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: FieldValidation;\n\twidget?: string;\n\toptions?: FieldWidgetOptions;\n\tsortOrder: number;\n\tsearchable: boolean;\n\t/** Whether this field is translatable (default true). Non-translatable fields are synced across locales. */\n\ttranslatable: boolean;\n\tcreatedAt: string;\n}\n\n/**\n * Input for creating a collection\n */\nexport interface CreateCollectionInput {\n\tslug: string;\n\tlabel: string;\n\tlabelSingular?: string;\n\tdescription?: string;\n\ticon?: string;\n\tsupports?: CollectionSupport[];\n\tsource?: CollectionSource;\n\turlPattern?: string;\n\thasSeo?: boolean;\n\tcommentsEnabled?: boolean;\n}\n\n/**\n * Input for updating a collection\n */\nexport interface UpdateCollectionInput {\n\tlabel?: string;\n\tlabelSingular?: string;\n\tdescription?: string;\n\ticon?: string;\n\tsupports?: CollectionSupport[];\n\turlPattern?: string;\n\thasSeo?: boolean;\n\tcommentsEnabled?: boolean;\n\tcommentsModeration?: \"all\" | \"first_time\" | \"none\";\n\tcommentsClosedAfterDays?: number;\n\tcommentsAutoApproveUsers?: boolean;\n}\n\n/**\n * Input for creating a field\n */\nexport interface CreateFieldInput {\n\tslug: string;\n\tlabel: string;\n\ttype: FieldType;\n\trequired?: boolean;\n\tunique?: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: FieldValidation | null;\n\twidget?: string;\n\toptions?: FieldWidgetOptions;\n\tsortOrder?: number;\n\t/** Whether this field should be indexed for search */\n\tsearchable?: boolean;\n\t/** Whether this field is translatable (default true). Non-translatable fields are synced across locales. */\n\ttranslatable?: boolean;\n}\n\n/**\n * Input for updating a field\n */\nexport interface UpdateFieldInput {\n\tlabel?: string;\n\trequired?: boolean;\n\tunique?: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: FieldValidation | null;\n\twidget?: string;\n\toptions?: FieldWidgetOptions;\n\tsortOrder?: number;\n\t/** Whether this field should be indexed for search */\n\tsearchable?: boolean;\n\t/** Whether this field is translatable (default true). Non-translatable fields are synced across locales. */\n\ttranslatable?: boolean;\n}\n\n/**\n * A collection with its fields\n */\nexport interface CollectionWithFields extends Collection {\n\tfields: Field[];\n}\n\n/**\n * Reserved field slugs that cannot be used.\n *\n * Includes names reserved for runtime hydration (`terms`, `bylines`, `byline`)\n * so user-defined fields never shadow the auto-hydrated values on entry.data.\n */\nexport const RESERVED_FIELD_SLUGS = [\n\t\"id\",\n\t\"slug\",\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// Runtime-hydrated fields\n\t\"terms\",\n\t\"bylines\",\n\t\"byline\",\n];\n\n/**\n * Reserved collection slugs that cannot be used\n */\nexport const RESERVED_COLLECTION_SLUGS = [\n\t\"content\",\n\t\"media\",\n\t\"users\",\n\t\"revisions\",\n\t\"taxonomies\",\n\t\"options\",\n\t\"audit_logs\",\n];\n\n/**\n * Byline custom fields (Discussion #1174).\n *\n * Sites declare site-specific byline metadata (`job_title`, `pronouns`,\n * `twitter_handle`, `company`, …) without touching emdash core. Definitions\n * live in `_emdash_byline_fields`; values in either\n * `_emdash_byline_field_values` (translatable, keyed by `byline_id`) or\n * `_emdash_byline_field_group_values` (non-translatable, keyed by\n * `translation_group`). The per-field `translatable` flag decides which\n * value table is used. See migration 041.\n */\n\n/**\n * The five v1 field types supported on byline custom fields. Deliberately\n * narrower than the content `FieldType` union — bylines don't need\n * `portableText`, `reference`, `image`, etc. v2 may extend this; v1 keeps\n * the storage and UI surfaces small.\n */\nexport type BylineFieldType = \"string\" | \"text\" | \"url\" | \"boolean\" | \"select\";\n\nexport const BYLINE_FIELD_TYPES: readonly BylineFieldType[] = [\n\t\"string\",\n\t\"text\",\n\t\"url\",\n\t\"boolean\",\n\t\"select\",\n] as const;\n\n/**\n * Validation rules for a byline custom field. v1 only exposes `options`\n * (the choice list for `select` fields). The shape mirrors the content-\n * field convention so the admin UI patterns transfer.\n */\nexport interface BylineFieldValidation {\n\t/** Choices for `select`-type fields. Ignored for other types. */\n\toptions?: string[];\n}\n\n/**\n * Runtime shape of a registered byline custom field. Stored in\n * `_emdash_byline_fields` (see migration 041).\n */\nexport interface BylineFieldDefinition {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\ttype: BylineFieldType;\n\trequired: boolean;\n\t/**\n\t * Whether values are stored per-locale (`true`, in\n\t * `_emdash_byline_field_values` keyed by `byline_id`) or shared across\n\t * every locale variant of the same byline identity (`false`, in\n\t * `_emdash_byline_field_group_values` keyed by `translation_group`).\n\t * Defaults to `true` at the DB level.\n\t */\n\ttranslatable: boolean;\n\tvalidation: BylineFieldValidation | null;\n\tsortOrder: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/**\n * Input for creating a byline custom field. `slug` and `type` are not\n * updatable post-create — changing either would invalidate stored values.\n */\nexport interface CreateBylineFieldInput {\n\tslug: string;\n\tlabel: string;\n\ttype: BylineFieldType;\n\trequired?: boolean;\n\ttranslatable?: boolean;\n\tvalidation?: BylineFieldValidation | null;\n\tsortOrder?: number;\n}\n\n/**\n * Input for updating a byline custom field. `slug` and `type` are\n * intentionally not present — see `CreateBylineFieldInput`.\n */\nexport interface UpdateBylineFieldInput {\n\tlabel?: string;\n\trequired?: boolean;\n\ttranslatable?: boolean;\n\tvalidation?: BylineFieldValidation | null;\n\tsortOrder?: number;\n}\n\n/**\n * Runtime value type for a byline custom field. The narrow union mirrors\n * what the five v1 field types can produce: `string`/`text`/`url`/`select`\n * → string, `boolean` → boolean, plus `null` for cleared values.\n */\nexport type CustomFieldValue = string | boolean | null;\n\n/**\n * Reserved byline-field slugs. Two reasons a slug ends up here:\n *\n * 1. **Column collision.** Slugs that match a fixed column on\n * `_emdash_bylines` (migrations 031 + 040) would shadow that column\n * on hydration. The first 12 entries cover this.\n * 2. **Route collision.** Static file routes under\n * `/_emdash/api/admin/byline-fields/` take precedence over the\n * `[slug].ts` dynamic route in Astro, so a custom field whose slug\n * matches a sibling static file (e.g. `reorder.ts`) is unreachable\n * via single-field CRUD — the static route handles only its own\n * method (POST for `reorder`) and 405s everything else.\n * `reorder` is the only such sibling today; new sibling routes\n * (e.g. a hypothetical `import.ts`) must be added here.\n * `[slug]/usage.ts` lives a level deeper so a slug of `usage` does\n * not collide — it resolves cleanly to `[slug].ts`.\n *\n * Enforced at the registry layer (Phase 2) and the admin API zod layer\n * (Phase 4) so non-HTTP callers (seeds, scripts) get the same guarantee.\n */\nexport const RESERVED_BYLINE_FIELD_SLUGS = [\n\t// 1. Column-collision slugs (matches `_emdash_bylines` fixed columns).\n\t\"id\",\n\t\"slug\",\n\t\"display_name\",\n\t\"bio\",\n\t\"avatar_media_id\",\n\t\"website_url\",\n\t\"user_id\",\n\t\"is_guest\",\n\t\"locale\",\n\t\"translation_group\",\n\t\"created_at\",\n\t\"updated_at\",\n\t// 2. Route-collision slugs (matches static sibling files of `[slug].ts`).\n\t\"reorder\",\n] as const;\n"],"mappings":";;;;AA+BA,MAAa,cAAoC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;;;AAUD,MAAa,uBAAsD;CAClE,QAAQ;CACR,MAAM;CACN,QAAQ;CACR,SAAS;CACT,SAAS;CACT,UAAU;CACV,QAAQ;CACR,aAAa;CACb,cAAc;CACd,OAAO;CACP,MAAM;CACN,WAAW;CACX,MAAM;CACN,MAAM;CACN,KAAK;CACL,UAAU;CACV;;;;;;;AA+MD,MAAa,uBAAuB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;;;;AAKD,MAAa,4BAA4B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAsBD,MAAa,qBAAiD;CAC7D;CACA;CACA;CACA;CACA;CACA;;;;;;;;;;;;;;;;;;;;;AAyFD,MAAa,8BAA8B;CAE1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-DbCWhHet.d.mts","names":[],"sources":["../src/schema/types.ts"],"mappings":";;AAUA;;;;;AA2CA;;;AAAA,KA3CY,SAAA;;;;KA2CA,UAAA;;;;cAKC,oBAAA,EAAsB,MAAA,CAAO,SAAA,EAAW,UAAA;;;AAsBrD;KAAY,iBAAA;;;;KAWA,gBAAA;;;;;UAWK,gBAAA;EAChB,IAAA;EACA,IAAA;EACA,KAAA;EACA,QAAA;EACA,OAAA;AAAA;AAAA,UAegB,eAAA;EAChB,QAAA;EACA,GAAA;EACA,GAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA,GAAY,gBAAA;EACZ,QAAA;EACA,QAAA;EACA,gBAAA;AAAA;;;;UAMgB,kBAAA;EAChB,IAAA;EACA,WAAA;EACA,UAAA;EACA,aAAA;EAAA,CACC,GAAA;AAAA;;;;UAMe,UAAA;EAChB,EAAA;EACA,IAAA;EACA,KAAA;EACA,aAAA;EACA,WAAA;EACA,IAAA;EACA,QAAA,EAAU,iBAAA;EACV,MAAA,GAAS,gBAAA;EAPT;EASA,MAAA;EAPA;EASA,UAAA;EAPA;EASA,eAAA;EAPA;EASA,kBAAA;EARA;EAUA,uBAAA;EARA;EAUA,wBAAA;EACA,SAAA;EACA,SAAA;AAAA;;;;UAMgB,KAAA;EAChB,EAAA;EACA,YAAA;EACA,IAAA;EACA,KAAA;EACA,IAAA,EAAM,SAAA;EACN,UAAA,EAAY,UAAA;EACZ,QAAA;EACA,MAAA;EACA,YAAA;EACA,UAAA,GAAa,eAAA;EACb,MAAA;EACA,OAAA,GAAU,kBAAA;EACV,SAAA;EACA,UAAA;EATA;EAWA,YAAA;EACA,SAAA;AAAA;;;;UAMgB,qBAAA;EAChB,IAAA;EACA,KAAA;EACA,aAAA;EACA,WAAA;EACA,IAAA;EACA,QAAA,GAAW,iBAAA;EACX,MAAA,GAAS,gBAAA;EACT,UAAA;EACA,MAAA;EACA,eAAA;AAAA;;;;UAMgB,qBAAA;EAChB,KAAA;EACA,aAAA;EACA,WAAA;EACA,IAAA;EACA,QAAA,GAAW,iBAAA;EACX,UAAA;EACA,MAAA;EACA,eAAA;EACA,kBAAA;EACA,uBAAA;EACA,wBAAA;AAAA;;AAXD;;UAiBiB,gBAAA;EAChB,IAAA;EACA,KAAA;EACA,IAAA,EAAM,SAAA;EACN,QAAA;EACA,MAAA;EACA,YAAA;EACA,UAAA,GAAa,eAAA;EACb,MAAA;EACA,OAAA,GAAU,kBAAA;EACV,SAAA;EAlBA;EAoBA,UAAA;EAlBA;EAoBA,YAAA;AAAA;AAdD;;;AAAA,UAoBiB,gBAAA;EAChB,KAAA;EACA,QAAA;EACA,MAAA;EACA,YAAA;EACA,UAAA,GAAa,eAAA;EACb,MAAA;EACA,OAAA,GAAU,kBAAA;EACV,SAAA;EAxBA;EA0BA,UAAA;EAxBA;EA0BA,YAAA;AAAA;;;;UAMgB,oBAAA,SAA6B,UAAA;EAC7C,MAAA,EAAQ,KAAA;AAAA;;;AAnBT;;;;cA4Ba,oBAAA;;;;cAuBA,yBAAA;;;;;;KAuGD,gBAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-i8_uzhMD.d.mts","names":[],"sources":["../src/database/repositories/types.ts"],"mappings":";;;UAaiB,kBAAA;EAChB,IAAA;EACA,IAAA;EACA,IAAA,EAAM,MAAA;EACN,MAAA;EACA,QAAA;EACA,eAAA;EACA,MAAA;EACA,aAAA;EACA,WAAA;EALA;EAOA,SAAA;AAAA;AAAA,UAGgB,kBAAA;EAChB,IAAA,GAAO,MAAA;EACP,MAAA;EACA,IAAA;EACA,WAAA;EACA,WAAA;EACA,QAAA;EACA,eAAA;AAAA;;UAIgB,UAAA;EAChB,KAAA;EACA,WAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;AAAA;;UAIgB,eAAA;EAChB,KAAA;EACA,WAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,UAGgB,aAAA;EAChB,EAAA;EACA,IAAA;EACA,WAAA;EACA,GAAA;EACA,aAAA;EAbgB;;;;;;;;;;;AAQjB;;;;;EAsBC,gBAAA;EAnBA;EAqBA,SAAA;EACA,UAAA;EACA,MAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;EAFA;;;;EAOA,MAAA;EAkBA;;;;;AAGD;;EAbC,gBAAA;EAcqB;;;;;;;;AAOtB;EAXC,YAAA,GAAe,MAAA,SAAe,gBAAA;AAAA;AAAA,UAGd,mBAAA;EAChB,MAAA,EAAQ,aAAA;EACR,SAAA;EACA,SAAA;EASC;EAPD,MAAA;AAAA;AAAA,UAGgB,eAAA;EAChB,KAAA;IACC,MAAA;IACA,QAAA;IACA,MAAA,WAeK;IAbL,CAAA;IAgBe;;;;;IAVf,aAAA;EAAA;EAED,OAAA;IACC,KAAA;IACA,SAAA;EAAA;EAED,KAAA;EACA,MAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,KAAA,EAAO,CAAA;EACP,UAAA;EAuBY;;;AAgCb;;EAjDC,KAAA;AAAA;;;;;;;;;cAgBY,kBAAA,SAA2B,KAAA;cAC3B,MAAA;AAAA;AAAA,UAgCI,WAAA;EAChB,EAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,MAAA;EACN,QAAA;EACA,eAAA;EACA,MAAA,GAAS,aAAA;EACT,OAAA,GAAU,mBAAA;EACV,SAAA;EACA,SAAA;EACA,WAAA;EACA,WAAA;EACA,cAAA;EACA,eAAA;EACA,OAAA;EACA,MAAA;EACA,gBAAA;EAmByB;EAjBzB,GAAA,GAAM,UAAA;;;;;;;;;;;EAWN,QAAA,GAAW,MAAA;AAAA;AAAA,cAGC,qBAAA,SAA8B,KAAA;EAGlC,OAAA;cADP,OAAA,UACO,OAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"user-DzEUl5zA.mjs","names":[],"sources":["../src/database/repositories/user.ts"],"sourcesContent":["import type { Kysely, Selectable, Updateable } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { Database, UserTable } from \"../types.js\";\nimport { encodeCursor, decodeCursor, type FindManyResult } from \"./types.js\";\n\ntype UserRow = Selectable<UserTable>;\n\n/**\n * Valid role levels matching the database schema.\n * 10=subscriber, 20=contributor, 30=author, 40=editor, 50=admin\n */\nexport type UserRole = 10 | 20 | 30 | 40 | 50;\n\n/** String role names for convenience APIs */\nexport type UserRoleName = \"subscriber\" | \"contributor\" | \"author\" | \"editor\" | \"admin\";\n\nexport interface User {\n\tid: string;\n\temail: string;\n\tname: string | null;\n\trole: UserRole;\n\tavatarUrl: string | null;\n\temailVerified: boolean;\n\tdata: Record<string, unknown> | null;\n\tcreatedAt: string;\n}\n\nexport interface CreateUserInput {\n\temail: string;\n\tname?: string;\n\trole?: UserRole | UserRoleName;\n\tavatarUrl?: string;\n\tdata?: Record<string, unknown>;\n}\n\nexport interface UpdateUserInput {\n\tname?: string;\n\trole?: UserRole | UserRoleName;\n\tavatarUrl?: string | null;\n\tdata?: Record<string, unknown>;\n}\n\n/**\n * User repository for CRUD operations\n */\nexport class UserRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new user\n\t */\n\tasync create(input: CreateUserInput): Promise<User> {\n\t\tconst id = ulid();\n\n\t\tconst row: Omit<UserTable, \"created_at\" | \"updated_at\" | \"disabled\"> = {\n\t\t\tid,\n\t\t\temail: input.email.toLowerCase(),\n\t\t\tname: input.name ?? null,\n\t\t\trole: UserRepository.resolveRole(input.role ?? 10),\n\t\t\tavatar_url: input.avatarUrl ?? null,\n\t\t\temail_verified: 0,\n\t\t\tdata: input.data ? JSON.stringify(input.data) : null,\n\t\t};\n\n\t\tawait this.db.insertInto(\"users\").values(row).execute();\n\n\t\tconst user = await this.findById(id);\n\t\tif (!user) {\n\t\t\tthrow new Error(\"Failed to create user\");\n\t\t}\n\t\treturn user;\n\t}\n\n\t/**\n\t * Find user by ID\n\t */\n\tasync findById(id: string): Promise<User | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToUser(row) : null;\n\t}\n\n\t/**\n\t * Find user by email (case-insensitive)\n\t */\n\tasync findByEmail(email: string): Promise<User | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.where(\"email\", \"=\", email.toLowerCase())\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToUser(row) : null;\n\t}\n\n\t/**\n\t * List all users with cursor-based pagination\n\t */\n\tasync findMany(\n\t\toptions: {\n\t\t\trole?: UserRole | UserRoleName;\n\t\t\tlimit?: number;\n\t\t\tcursor?: string;\n\t\t} = {},\n\t): Promise<FindManyResult<User>> {\n\t\tconst limit = Math.min(Math.max(1, options.limit || 50), 100);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tif (options.role !== undefined) {\n\t\t\tquery = query.where(\"role\", \"=\", UserRepository.resolveRole(options.role));\n\t\t}\n\n\t\tif (options.cursor) {\n\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\tquery = query.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"created_at\", \"<\", decoded.orderValue),\n\t\t\t\t\teb.and([eb(\"created_at\", \"=\", decoded.orderValue), eb(\"id\", \"<\", decoded.id)]),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\tconst rows = await query.execute();\n\t\tconst items = rows.slice(0, limit).map((row) => this.rowToUser(row));\n\t\tconst result: FindManyResult<User> = { items };\n\n\t\tif (rows.length > limit && items.length > 0) {\n\t\t\tconst last = items.at(-1)!;\n\t\t\tresult.nextCursor = encodeCursor(last.createdAt, last.id);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Update a user\n\t */\n\tasync update(id: string, input: UpdateUserInput): Promise<User | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) return null;\n\n\t\tconst updates: Updateable<UserTable> = {};\n\t\tif (input.name !== undefined) updates.name = input.name;\n\t\tif (input.role !== undefined) updates.role = UserRepository.resolveRole(input.role);\n\t\tif (input.avatarUrl !== undefined) updates.avatar_url = input.avatarUrl;\n\t\tif (input.data !== undefined) updates.data = JSON.stringify(input.data);\n\n\t\tif (Object.keys(updates).length > 0) {\n\t\t\tawait this.db.updateTable(\"users\").set(updates).where(\"id\", \"=\", id).execute();\n\t\t}\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Delete a user\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db.deleteFrom(\"users\").where(\"id\", \"=\", id).executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Count users\n\t */\n\tasync count(role?: UserRole | UserRoleName): Promise<number> {\n\t\tlet query = this.db.selectFrom(\"users\").select((eb) => eb.fn.count(\"id\").as(\"count\"));\n\n\t\tif (role !== undefined) {\n\t\t\tquery = query.where(\"role\", \"=\", UserRepository.resolveRole(role));\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Check if email exists\n\t */\n\tasync emailExists(email: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"email\", \"=\", email.toLowerCase())\n\t\t\t.executeTakeFirst();\n\n\t\treturn !!row;\n\t}\n\n\t/**\n\t * Convert database row to User object\n\t */\n\tprivate rowToUser(row: UserRow): User {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\temail: row.email,\n\t\t\tname: row.name,\n\t\t\trole: UserRepository.toRole(row.role),\n\t\t\tavatarUrl: row.avatar_url,\n\t\t\temailVerified: row.email_verified === 1,\n\t\t\tdata: row.data ? JSON.parse(row.data) : null,\n\t\t\tcreatedAt: row.created_at,\n\t\t};\n\t}\n\n\t/** Map of role name strings to numeric levels */\n\tprivate static readonly ROLE_NAME_TO_LEVEL: Record<UserRoleName, UserRole> = {\n\t\tsubscriber: 10,\n\t\tcontributor: 20,\n\t\tauthor: 30,\n\t\teditor: 40,\n\t\tadmin: 50,\n\t};\n\n\t/** Valid numeric role levels */\n\tprivate static readonly VALID_LEVELS = new Set<number>([10, 20, 30, 40, 50]);\n\n\t/**\n\t * Resolve a role name or number to a valid numeric UserRole.\n\t * Accepts both string names (\"admin\") and numeric levels (50).\n\t */\n\tstatic resolveRole(role: UserRole | UserRoleName): UserRole {\n\t\tif (typeof role === \"string\") {\n\t\t\tconst level = UserRepository.ROLE_NAME_TO_LEVEL[role];\n\t\t\tif (level === undefined) {\n\t\t\t\tthrow new Error(`Invalid role name: ${role}`);\n\t\t\t}\n\t\t\treturn level;\n\t\t}\n\t\tif (!UserRepository.VALID_LEVELS.has(role)) {\n\t\t\tthrow new Error(`Invalid role level: ${role}`);\n\t\t}\n\t\treturn role;\n\t}\n\n\t/**\n\t * Convert a raw DB integer to a typed UserRole.\n\t * Falls back to subscriber (10) for unknown values.\n\t */\n\tprivate static toRole(level: number): UserRole {\n\t\tif (UserRepository.VALID_LEVELS.has(level)) return level as UserRole;\n\t\treturn 10;\n\t}\n}\n"],"mappings":";;;;;;;AA8CA,IAAa,iBAAb,MAAa,eAAe;CAC3B,YAAY,AAAQ,IAAsB;EAAtB;;;;;CAKpB,MAAM,OAAO,OAAuC;EACnD,MAAM,KAAK,MAAM;EAEjB,MAAM,MAAiE;GACtE;GACA,OAAO,MAAM,MAAM,aAAa;GAChC,MAAM,MAAM,QAAQ;GACpB,MAAM,eAAe,YAAY,MAAM,QAAQ,GAAG;GAClD,YAAY,MAAM,aAAa;GAC/B,gBAAgB;GAChB,MAAM,MAAM,OAAO,KAAK,UAAU,MAAM,KAAK,GAAG;GAChD;AAED,QAAM,KAAK,GAAG,WAAW,QAAQ,CAAC,OAAO,IAAI,CAAC,SAAS;EAEvD,MAAM,OAAO,MAAM,KAAK,SAAS,GAAG;AACpC,MAAI,CAAC,KACJ,OAAM,IAAI,MAAM,wBAAwB;AAEzC,SAAO;;;;;CAMR,MAAM,SAAS,IAAkC;EAChD,MAAM,MAAM,MAAM,KAAK,GACrB,WAAW,QAAQ,CACnB,WAAW,CACX,MAAM,MAAM,KAAK,GAAG,CACpB,kBAAkB;AAEpB,SAAO,MAAM,KAAK,UAAU,IAAI,GAAG;;;;;CAMpC,MAAM,YAAY,OAAqC;EACtD,MAAM,MAAM,MAAM,KAAK,GACrB,WAAW,QAAQ,CACnB,WAAW,CACX,MAAM,SAAS,KAAK,MAAM,aAAa,CAAC,CACxC,kBAAkB;AAEpB,SAAO,MAAM,KAAK,UAAU,IAAI,GAAG;;;;;CAMpC,MAAM,SACL,UAII,EAAE,EAC0B;EAChC,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,GAAG,QAAQ,SAAS,GAAG,EAAE,IAAI;EAE7D,IAAI,QAAQ,KAAK,GACf,WAAW,QAAQ,CACnB,WAAW,CACX,QAAQ,cAAc,OAAO,CAC7B,QAAQ,MAAM,OAAO,CACrB,MAAM,QAAQ,EAAE;AAElB,MAAI,QAAQ,SAAS,OACpB,SAAQ,MAAM,MAAM,QAAQ,KAAK,eAAe,YAAY,QAAQ,KAAK,CAAC;AAG3E,MAAI,QAAQ,QAAQ;GACnB,MAAM,UAAU,aAAa,QAAQ,OAAO;AAC5C,WAAQ,MAAM,OAAO,OACpB,GAAG,GAAG,CACL,GAAG,cAAc,KAAK,QAAQ,WAAW,EACzC,GAAG,IAAI,CAAC,GAAG,cAAc,KAAK,QAAQ,WAAW,EAAE,GAAG,MAAM,KAAK,QAAQ,GAAG,CAAC,CAAC,CAC9E,CAAC,CACF;;EAGF,MAAM,OAAO,MAAM,MAAM,SAAS;EAClC,MAAM,QAAQ,KAAK,MAAM,GAAG,MAAM,CAAC,KAAK,QAAQ,KAAK,UAAU,IAAI,CAAC;EACpE,MAAM,SAA+B,EAAE,OAAO;AAE9C,MAAI,KAAK,SAAS,SAAS,MAAM,SAAS,GAAG;GAC5C,MAAM,OAAO,MAAM,GAAG,GAAG;AACzB,UAAO,aAAa,aAAa,KAAK,WAAW,KAAK,GAAG;;AAG1D,SAAO;;;;;CAMR,MAAM,OAAO,IAAY,OAA8C;AAEtE,MAAI,CADa,MAAM,KAAK,SAAS,GAAG,CACzB,QAAO;EAEtB,MAAM,UAAiC,EAAE;AACzC,MAAI,MAAM,SAAS,OAAW,SAAQ,OAAO,MAAM;AACnD,MAAI,MAAM,SAAS,OAAW,SAAQ,OAAO,eAAe,YAAY,MAAM,KAAK;AACnF,MAAI,MAAM,cAAc,OAAW,SAAQ,aAAa,MAAM;AAC9D,MAAI,MAAM,SAAS,OAAW,SAAQ,OAAO,KAAK,UAAU,MAAM,KAAK;AAEvE,MAAI,OAAO,KAAK,QAAQ,CAAC,SAAS,EACjC,OAAM,KAAK,GAAG,YAAY,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,MAAM,KAAK,GAAG,CAAC,SAAS;AAG/E,SAAO,KAAK,SAAS,GAAG;;;;;CAMzB,MAAM,OAAO,IAA8B;AAG1C,WAFe,MAAM,KAAK,GAAG,WAAW,QAAQ,CAAC,MAAM,MAAM,KAAK,GAAG,CAAC,kBAAkB,EAEzE,kBAAkB,KAAK;;;;;CAMvC,MAAM,MAAM,MAAiD;EAC5D,IAAI,QAAQ,KAAK,GAAG,WAAW,QAAQ,CAAC,QAAQ,OAAO,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC;AAErF,MAAI,SAAS,OACZ,SAAQ,MAAM,MAAM,QAAQ,KAAK,eAAe,YAAY,KAAK,CAAC;EAGnE,MAAM,SAAS,MAAM,MAAM,kBAAkB;AAC7C,SAAO,OAAO,QAAQ,SAAS,EAAE;;;;;CAMlC,MAAM,YAAY,OAAiC;AAOlD,SAAO,CAAC,CANI,MAAM,KAAK,GACrB,WAAW,QAAQ,CACnB,OAAO,KAAK,CACZ,MAAM,SAAS,KAAK,MAAM,aAAa,CAAC,CACxC,kBAAkB;;;;;CAQrB,AAAQ,UAAU,KAAoB;AACrC,SAAO;GACN,IAAI,IAAI;GACR,OAAO,IAAI;GACX,MAAM,IAAI;GACV,MAAM,eAAe,OAAO,IAAI,KAAK;GACrC,WAAW,IAAI;GACf,eAAe,IAAI,mBAAmB;GACtC,MAAM,IAAI,OAAO,KAAK,MAAM,IAAI,KAAK,GAAG;GACxC,WAAW,IAAI;GACf;;;CAIF,OAAwB,qBAAqD;EAC5E,YAAY;EACZ,aAAa;EACb,QAAQ;EACR,QAAQ;EACR,OAAO;EACP;;CAGD,OAAwB,eAAe,IAAI,IAAY;EAAC;EAAI;EAAI;EAAI;EAAI;EAAG,CAAC;;;;;CAM5E,OAAO,YAAY,MAAyC;AAC3D,MAAI,OAAO,SAAS,UAAU;GAC7B,MAAM,QAAQ,eAAe,mBAAmB;AAChD,OAAI,UAAU,OACb,OAAM,IAAI,MAAM,sBAAsB,OAAO;AAE9C,UAAO;;AAER,MAAI,CAAC,eAAe,aAAa,IAAI,KAAK,CACzC,OAAM,IAAI,MAAM,uBAAuB,OAAO;AAE/C,SAAO;;;;;;CAOR,OAAe,OAAO,OAAyB;AAC9C,MAAI,eAAe,aAAa,IAAI,MAAM,CAAE,QAAO;AACnD,SAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-Dy6nkNls.d.mts","names":[],"sources":["../src/seed/types.ts","../src/seed/apply.ts","../src/seed/default.ts","../src/seed/load.ts","../src/seed/validate.ts"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-JCXcsqiY.mjs","names":[],"sources":["../src/seed/validate.ts"],"sourcesContent":["/**\n * Seed file validation\n *\n * Validates a seed file structure before applying it.\n */\n\nimport { FIELD_TYPES } from \"../schema/types.js\";\nimport type { SeedFile, SeedMenuItem, ValidationResult } from \"./types.js\";\n\nconst COLLECTION_FIELD_SLUG_PATTERN = /^[a-z][a-z0-9_]*$/;\nconst SLUG_PATTERN = /^[a-z0-9-]+$/;\nconst REDIRECT_TYPES = new Set([301, 302, 307, 308]);\nconst CRLF_PATTERN = /[\\r\\n]/;\n\n/** Type guard for Record<string, unknown> */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isValidRedirectPath(path: string): boolean {\n\tif (!path.startsWith(\"/\") || path.startsWith(\"//\") || CRLF_PATTERN.test(path)) {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\treturn !decodeURIComponent(path).split(\"/\").includes(\"..\");\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Validate a seed file\n *\n * @param data - Unknown data to validate as a seed file\n * @returns Validation result with errors and warnings\n */\nexport function validateSeed(data: unknown): ValidationResult {\n\tconst errors: string[] = [];\n\tconst warnings: string[] = [];\n\n\t// Basic type check\n\tif (!data || typeof data !== \"object\") {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terrors: [\"Seed must be an object\"],\n\t\t\twarnings: [],\n\t\t};\n\t}\n\n\tconst seed = data as Partial<SeedFile>;\n\n\t// Required fields\n\tif (!seed.version) {\n\t\terrors.push(\"Seed must have a version field\");\n\t} else if (seed.version !== \"1\") {\n\t\terrors.push(`Unsupported seed version: ${String(seed.version)}`);\n\t}\n\n\t// Validate collections\n\tif (seed.collections) {\n\t\tif (!Array.isArray(seed.collections)) {\n\t\t\terrors.push(\"collections must be an array\");\n\t\t} else {\n\t\t\tconst collectionSlugs = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.collections.length; i++) {\n\t\t\t\tconst collection = seed.collections[i];\n\t\t\t\tconst prefix = `collections[${i}]`;\n\n\t\t\t\tif (!collection.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Check for valid slug format\n\t\t\t\t\tif (!COLLECTION_FIELD_SLUG_PATTERN.test(collection.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for duplicate slugs\n\t\t\t\t\tif (collectionSlugs.has(collection.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate collection slug \"${collection.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tcollectionSlugs.add(collection.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!collection.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\t// Validate fields\n\t\t\t\tif (!Array.isArray(collection.fields)) {\n\t\t\t\t\terrors.push(`${prefix}.fields: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tconst fieldSlugs = new Set<string>();\n\n\t\t\t\t\tfor (let j = 0; j < collection.fields.length; j++) {\n\t\t\t\t\t\tconst field = collection.fields[j];\n\t\t\t\t\t\tconst fieldPrefix = `${prefix}.fields[${j}]`;\n\n\t\t\t\t\t\tif (!field.slug) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: slug is required`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Check for valid slug format\n\t\t\t\t\t\t\tif (!COLLECTION_FIELD_SLUG_PATTERN.test(field.slug)) {\n\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t`${fieldPrefix}.slug: must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check for duplicate field slugs\n\t\t\t\t\t\t\tif (fieldSlugs.has(field.slug)) {\n\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t`${fieldPrefix}.slug: duplicate field slug \"${field.slug}\" in collection \"${collection.slug}\"`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfieldSlugs.add(field.slug);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!field.label) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: label is required`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!field.type) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}: type is required`);\n\t\t\t\t\t\t} else if (!(FIELD_TYPES as readonly string[]).includes(field.type)) {\n\t\t\t\t\t\t\terrors.push(`${fieldPrefix}.type: unsupported field type \"${field.type}\"`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate taxonomies\n\tif (seed.taxonomies) {\n\t\tif (!Array.isArray(seed.taxonomies)) {\n\t\t\terrors.push(\"taxonomies must be an array\");\n\t\t} else {\n\t\t\tconst taxonomyNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.taxonomies.length; i++) {\n\t\t\t\tconst taxonomy = seed.taxonomies[i];\n\t\t\t\tconst prefix = `taxonomies[${i}]`;\n\n\t\t\t\tif (!taxonomy.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Uniqueness is per (name, locale).\n\t\t\t\t\tconst key = `${taxonomy.name}::${taxonomy.locale ?? \"\"}`;\n\t\t\t\t\tif (taxonomyNames.has(key)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\ttaxonomy.locale\n\t\t\t\t\t\t\t\t? `${prefix}.name: duplicate taxonomy \"${taxonomy.name}\" in locale \"${taxonomy.locale}\"`\n\t\t\t\t\t\t\t\t: `${prefix}.name: duplicate taxonomy name \"${taxonomy.name}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\ttaxonomyNames.add(key);\n\t\t\t\t}\n\n\t\t\t\tif (!taxonomy.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (taxonomy.hierarchical === undefined) {\n\t\t\t\t\terrors.push(`${prefix}: hierarchical is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(taxonomy.collections)) {\n\t\t\t\t\terrors.push(`${prefix}.collections: must be an array`);\n\t\t\t\t} else if (taxonomy.collections.length === 0) {\n\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t`${prefix}.collections: taxonomy \"${taxonomy.name}\" is not assigned to any collections`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Validate terms if present\n\t\t\t\tif (taxonomy.terms) {\n\t\t\t\t\tif (!Array.isArray(taxonomy.terms)) {\n\t\t\t\t\t\terrors.push(`${prefix}.terms: must be an array`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst termSlugs = new Set<string>();\n\n\t\t\t\t\t\tfor (let j = 0; j < taxonomy.terms.length; j++) {\n\t\t\t\t\t\t\tconst term = taxonomy.terms[j];\n\t\t\t\t\t\t\tconst termPrefix = `${prefix}.terms[${j}]`;\n\n\t\t\t\t\t\t\tif (!term.slug) {\n\t\t\t\t\t\t\t\terrors.push(`${termPrefix}: slug is required`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Uniqueness is per (slug, locale) so the same slug can repeat\n\t\t\t\t\t\t\t\t// across locale variants of the def.\n\t\t\t\t\t\t\t\tconst key = `${term.slug}::${term.locale ?? taxonomy.locale ?? \"\"}`;\n\t\t\t\t\t\t\t\tif (termSlugs.has(key)) {\n\t\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t\t`${termPrefix}.slug: duplicate term slug \"${term.slug}\" in taxonomy \"${taxonomy.name}\"`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttermSlugs.add(key);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (!term.label) {\n\t\t\t\t\t\t\t\terrors.push(`${termPrefix}: label is required`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check parent reference validity (for hierarchical taxonomies)\n\t\t\t\t\t\t\tif (term.parent && taxonomy.hierarchical) {\n\t\t\t\t\t\t\t\t// Parent will be validated in a second pass\n\t\t\t\t\t\t\t} else if (term.parent && !taxonomy.hierarchical) {\n\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t`${termPrefix}.parent: taxonomy \"${taxonomy.name}\" is not hierarchical, parent will be ignored`,\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\n\t\t\t\t\t\t// Second pass: validate parent references (within the same locale).\n\t\t\t\t\t\tif (taxonomy.hierarchical && taxonomy.terms) {\n\t\t\t\t\t\t\tfor (let j = 0; j < taxonomy.terms.length; j++) {\n\t\t\t\t\t\t\t\tconst term = taxonomy.terms[j];\n\t\t\t\t\t\t\t\tconst termLocale = term.locale ?? taxonomy.locale ?? \"\";\n\t\t\t\t\t\t\t\tif (term.parent && !termSlugs.has(`${term.parent}::${termLocale}`)) {\n\t\t\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t\t\t`${prefix}.terms[${j}].parent: parent term \"${term.parent}\" not found in taxonomy`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Check for circular references\n\t\t\t\t\t\t\t\tif (term.parent === term.slug) {\n\t\t\t\t\t\t\t\t\terrors.push(`${prefix}.terms[${j}].parent: term cannot be its own parent`);\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}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate menus\n\tif (seed.menus) {\n\t\tif (!Array.isArray(seed.menus)) {\n\t\t\terrors.push(\"menus must be an array\");\n\t\t} else {\n\t\t\tconst menuNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.menus.length; i++) {\n\t\t\t\tconst menu = seed.menus[i];\n\t\t\t\tconst prefix = `menus[${i}]`;\n\n\t\t\t\tif (!menu.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Uniqueness is per (name, locale) — siblings of a translation\n\t\t\t\t\t// group share name but differ in locale.\n\t\t\t\t\tconst key = `${menu.name}::${menu.locale ?? \"\"}`;\n\t\t\t\t\tif (menuNames.has(key)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\tmenu.locale\n\t\t\t\t\t\t\t\t? `${prefix}.name: duplicate menu \"${menu.name}\" in locale \"${menu.locale}\"`\n\t\t\t\t\t\t\t\t: `${prefix}.name: duplicate menu name \"${menu.name}\"`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tmenuNames.add(key);\n\t\t\t\t}\n\n\t\t\t\tif (!menu.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(menu.items)) {\n\t\t\t\t\terrors.push(`${prefix}.items: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tvalidateMenuItems(menu.items, prefix, errors, warnings);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate redirects\n\tif (seed.redirects) {\n\t\tif (!Array.isArray(seed.redirects)) {\n\t\t\terrors.push(\"redirects must be an array\");\n\t\t} else {\n\t\t\tconst redirectSources = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.redirects.length; i++) {\n\t\t\t\tconst redirect = seed.redirects[i];\n\t\t\t\tconst prefix = `redirects[${i}]`;\n\n\t\t\t\tif (!isRecord(redirect)) {\n\t\t\t\t\terrors.push(`${prefix}: must be an object`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst source = typeof redirect.source === \"string\" ? redirect.source : undefined;\n\t\t\t\tconst destination =\n\t\t\t\t\ttypeof redirect.destination === \"string\" ? redirect.destination : undefined;\n\n\t\t\t\tif (!source) {\n\t\t\t\t\terrors.push(`${prefix}: source is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!isValidRedirectPath(source)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.source: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (redirectSources.has(source)) {\n\t\t\t\t\t\terrors.push(`${prefix}.source: duplicate redirect source \"${source}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tredirectSources.add(source);\n\t\t\t\t}\n\n\t\t\t\tif (!destination) {\n\t\t\t\t\terrors.push(`${prefix}: destination is required`);\n\t\t\t\t} else if (!isValidRedirectPath(destination)) {\n\t\t\t\t\terrors.push(\n\t\t\t\t\t\t`${prefix}.destination: must be a path starting with / (no protocol-relative URLs, path traversal, or newlines)`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (redirect.type !== undefined) {\n\t\t\t\t\tif (typeof redirect.type !== \"number\" || !REDIRECT_TYPES.has(redirect.type)) {\n\t\t\t\t\t\terrors.push(`${prefix}.type: must be 301, 302, 307, or 308`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (redirect.enabled !== undefined && typeof redirect.enabled !== \"boolean\") {\n\t\t\t\t\terrors.push(`${prefix}.enabled: must be a boolean`);\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tredirect.groupName !== undefined &&\n\t\t\t\t\ttypeof redirect.groupName !== \"string\" &&\n\t\t\t\t\tredirect.groupName !== null\n\t\t\t\t) {\n\t\t\t\t\terrors.push(`${prefix}.groupName: must be a string or null`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate widget areas\n\tif (seed.widgetAreas) {\n\t\tif (!Array.isArray(seed.widgetAreas)) {\n\t\t\terrors.push(\"widgetAreas must be an array\");\n\t\t} else {\n\t\t\tconst areaNames = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.widgetAreas.length; i++) {\n\t\t\t\tconst area = seed.widgetAreas[i];\n\t\t\t\tconst prefix = `widgetAreas[${i}]`;\n\n\t\t\t\tif (!area.name) {\n\t\t\t\t\terrors.push(`${prefix}: name is required`);\n\t\t\t\t} else {\n\t\t\t\t\t// Check for duplicate area names\n\t\t\t\t\tif (areaNames.has(area.name)) {\n\t\t\t\t\t\terrors.push(`${prefix}.name: duplicate widget area name \"${area.name}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tareaNames.add(area.name);\n\t\t\t\t}\n\n\t\t\t\tif (!area.label) {\n\t\t\t\t\terrors.push(`${prefix}: label is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(area.widgets)) {\n\t\t\t\t\terrors.push(`${prefix}.widgets: must be an array`);\n\t\t\t\t} else {\n\t\t\t\t\tfor (let j = 0; j < area.widgets.length; j++) {\n\t\t\t\t\t\tconst widget = area.widgets[j];\n\t\t\t\t\t\tconst widgetPrefix = `${prefix}.widgets[${j}]`;\n\n\t\t\t\t\t\tif (!widget.type) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: type is required`);\n\t\t\t\t\t\t} else if (![\"content\", \"menu\", \"component\"].includes(widget.type)) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}.type: must be \"content\", \"menu\", or \"component\"`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Type-specific validation\n\t\t\t\t\t\tif (widget.type === \"menu\" && !widget.menuName) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: menuName is required for menu widgets`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (widget.type === \"component\" && !widget.componentId) {\n\t\t\t\t\t\t\terrors.push(`${widgetPrefix}: componentId is required for component widgets`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate sections\n\tif (seed.sections) {\n\t\tif (!Array.isArray(seed.sections)) {\n\t\t\terrors.push(\"sections must be an array\");\n\t\t} else {\n\t\t\tconst sectionSlugs = new Set<string>();\n\n\t\t\tfor (let i = 0; i < seed.sections.length; i++) {\n\t\t\t\tconst section = seed.sections[i];\n\t\t\t\tconst prefix = `sections[${i}]`;\n\n\t\t\t\tif (!section.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!SLUG_PATTERN.test(section.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (sectionSlugs.has(section.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate section slug \"${section.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tsectionSlugs.add(section.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!section.title) {\n\t\t\t\t\terrors.push(`${prefix}: title is required`);\n\t\t\t\t}\n\n\t\t\t\tif (!Array.isArray(section.content)) {\n\t\t\t\t\terrors.push(`${prefix}.content: must be an array`);\n\t\t\t\t}\n\n\t\t\t\t// Validate source\n\t\t\t\tif (section.source && ![\"theme\", \"import\"].includes(section.source)) {\n\t\t\t\t\terrors.push(`${prefix}.source: must be \"theme\" or \"import\"`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate bylines\n\tif (seed.bylines) {\n\t\tif (!Array.isArray(seed.bylines)) {\n\t\t\terrors.push(\"bylines must be an array\");\n\t\t} else {\n\t\t\tconst bylineIds = new Set<string>();\n\t\t\tconst bylineSlugs = new Set<string>();\n\t\t\tfor (let i = 0; i < seed.bylines.length; i++) {\n\t\t\t\tconst byline = seed.bylines[i];\n\t\t\t\tconst prefix = `bylines[${i}]`;\n\n\t\t\t\tif (!byline.id) {\n\t\t\t\t\terrors.push(`${prefix}: id is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (bylineIds.has(byline.id)) {\n\t\t\t\t\t\terrors.push(`${prefix}.id: duplicate byline id \"${byline.id}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tbylineIds.add(byline.id);\n\t\t\t\t}\n\n\t\t\t\tif (!byline.slug) {\n\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t} else {\n\t\t\t\t\tif (!SLUG_PATTERN.test(byline.slug)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`${prefix}.slug: must contain only lowercase letters, numbers, and hyphens`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (bylineSlugs.has(byline.slug)) {\n\t\t\t\t\t\terrors.push(`${prefix}.slug: duplicate byline slug \"${byline.slug}\"`);\n\t\t\t\t\t}\n\t\t\t\t\tbylineSlugs.add(byline.slug);\n\t\t\t\t}\n\n\t\t\t\tif (!byline.displayName) {\n\t\t\t\t\terrors.push(`${prefix}: displayName is required`);\n\t\t\t\t}\n\n\t\t\t\tif (byline.avatar !== undefined) {\n\t\t\t\t\tconst avatar: unknown = byline.avatar;\n\t\t\t\t\tif (!isRecord(avatar)) {\n\t\t\t\t\t\terrors.push(`${prefix}.avatar: must be an object`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// storageKey is used verbatim in a `where storage_key = ?` lookup,\n\t\t\t\t\t\t// so surrounding whitespace would silently never match a real key.\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttypeof avatar.storageKey !== \"string\" ||\n\t\t\t\t\t\t\tavatar.storageKey.length === 0 ||\n\t\t\t\t\t\t\tavatar.storageKey !== avatar.storageKey.trim()\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t`${prefix}.avatar.storageKey: must be a non-empty string with no leading or trailing whitespace`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const key of [\"alt\", \"filename\", \"mimeType\"] as const) {\n\t\t\t\t\t\t\tif (avatar[key] !== undefined && typeof avatar[key] !== \"string\") {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must be a string`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// filename/mimeType are used verbatim, so reject blank or\n\t\t\t\t\t\t// whitespace-padded values (an empty filename would create a\n\t\t\t\t\t\t// media row with no basename; a padded mime type is invalid).\n\t\t\t\t\t\tfor (const key of [\"filename\", \"mimeType\"] as const) {\n\t\t\t\t\t\t\tconst v = avatar[key];\n\t\t\t\t\t\t\tif (typeof v === \"string\" && (v.length === 0 || v !== v.trim())) {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must not be empty or whitespace-padded`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const key of [\"width\", \"height\"] as const) {\n\t\t\t\t\t\t\tconst v = avatar[key];\n\t\t\t\t\t\t\tif (v !== undefined && (typeof v !== \"number\" || !Number.isFinite(v) || v < 0)) {\n\t\t\t\t\t\t\t\terrors.push(`${prefix}.avatar.${key}: must be a non-negative number`);\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\t\t\t}\n\t\t}\n\t}\n\n\t// Validate content\n\tif (seed.content) {\n\t\tif (typeof seed.content !== \"object\" || Array.isArray(seed.content)) {\n\t\t\terrors.push(\"content must be an object (collection -> entries)\");\n\t\t} else {\n\t\t\tfor (const [collectionSlug, entries] of Object.entries(seed.content)) {\n\t\t\t\tif (!Array.isArray(entries)) {\n\t\t\t\t\terrors.push(`content.${collectionSlug}: must be an array`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst entryIds = new Set<string>();\n\n\t\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\t\tconst entry = entries[i];\n\t\t\t\t\tconst prefix = `content.${collectionSlug}[${i}]`;\n\n\t\t\t\t\tif (!entry.id) {\n\t\t\t\t\t\terrors.push(`${prefix}: id is required`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Check for duplicate entry IDs\n\t\t\t\t\t\tif (entryIds.has(entry.id)) {\n\t\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t\t`${prefix}.id: duplicate entry id \"${entry.id}\" in collection \"${collectionSlug}\"`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tentryIds.add(entry.id);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!entry.slug) {\n\t\t\t\t\t\terrors.push(`${prefix}: slug is required`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!entry.data || typeof entry.data !== \"object\") {\n\t\t\t\t\t\terrors.push(`${prefix}: data must be an object`);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate i18n fields\n\t\t\t\t\tif (entry.translationOf) {\n\t\t\t\t\t\tif (!entry.locale) {\n\t\t\t\t\t\t\terrors.push(`${prefix}: locale is required when translationOf is set`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Second pass: validate translationOf references within this collection\n\t\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\t\tconst entry = entries[i];\n\t\t\t\t\tif (entry.translationOf && !entryIds.has(entry.translationOf)) {\n\t\t\t\t\t\terrors.push(\n\t\t\t\t\t\t\t`content.${collectionSlug}[${i}].translationOf: references \"${entry.translationOf}\" which is not in this collection`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate cross-references (content refs in menus)\n\tif (seed.menus && seed.content) {\n\t\tconst allContentIds = new Set<string>();\n\t\tfor (const entries of Object.values(seed.content)) {\n\t\t\tif (Array.isArray(entries)) {\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (entry.id) {\n\t\t\t\t\t\tallContentIds.add(entry.id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check menu item refs\n\t\tfor (const menu of seed.menus) {\n\t\t\tif (Array.isArray(menu.items)) {\n\t\t\t\tvalidateMenuItemRefs(menu.items, allContentIds, warnings);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate byline refs in content\n\tif (seed.content) {\n\t\tconst seedBylineIds = new Set<string>((seed.bylines ?? []).map((byline) => byline.id));\n\t\tfor (const [collectionSlug, entries] of Object.entries(seed.content)) {\n\t\t\tif (!Array.isArray(entries)) continue;\n\t\t\tfor (let i = 0; i < entries.length; i++) {\n\t\t\t\tconst entry = entries[i];\n\t\t\t\tif (!entry.bylines) continue;\n\t\t\t\tif (!Array.isArray(entry.bylines)) {\n\t\t\t\t\terrors.push(`content.${collectionSlug}[${i}].bylines: must be an array`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tfor (let j = 0; j < entry.bylines.length; j++) {\n\t\t\t\t\tconst credit = entry.bylines[j];\n\t\t\t\t\tconst prefix = `content.${collectionSlug}[${i}].bylines[${j}]`;\n\t\t\t\t\tif (!credit.byline) {\n\t\t\t\t\t\terrors.push(`${prefix}.byline: is required`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (!seedBylineIds.has(credit.byline)) {\n\t\t\t\t\t\terrors.push(`${prefix}.byline: references unknown byline \"${credit.byline}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tvalid: errors.length === 0,\n\t\terrors,\n\t\twarnings,\n\t};\n}\n\n/**\n * Validate menu items recursively\n */\nfunction validateMenuItems(\n\titems: unknown[],\n\tprefix: string,\n\terrors: string[],\n\twarnings: string[],\n): void {\n\tfor (let i = 0; i < items.length; i++) {\n\t\tconst raw = items[i];\n\t\tconst itemPrefix = `${prefix}.items[${i}]`;\n\n\t\tif (!isRecord(raw)) {\n\t\t\terrors.push(`${itemPrefix}: must be an object`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst item = raw;\n\n\t\tconst itemType = typeof item.type === \"string\" ? item.type : undefined;\n\n\t\tif (!itemType) {\n\t\t\terrors.push(`${itemPrefix}: type is required`);\n\t\t} else if (![\"custom\", \"page\", \"post\", \"taxonomy\", \"collection\"].includes(itemType)) {\n\t\t\terrors.push(\n\t\t\t\t`${itemPrefix}.type: must be \"custom\", \"page\", \"post\", \"taxonomy\", or \"collection\"`,\n\t\t\t);\n\t\t}\n\n\t\t// Type-specific validation\n\t\tif (itemType === \"custom\" && !item.url) {\n\t\t\terrors.push(`${itemPrefix}: url is required for custom menu items`);\n\t\t}\n\n\t\tif ((itemType === \"page\" || itemType === \"post\") && !item.ref) {\n\t\t\terrors.push(`${itemPrefix}: ref is required for page/post menu items`);\n\t\t}\n\n\t\t// Validate children recursively\n\t\tif (Array.isArray(item.children)) {\n\t\t\tvalidateMenuItems(item.children, itemPrefix, errors, warnings);\n\t\t}\n\t}\n}\n\n/**\n * Validate menu item references exist in content\n */\nfunction validateMenuItemRefs(\n\titems: SeedMenuItem[],\n\tcontentIds: Set<string>,\n\twarnings: string[],\n): void {\n\tfor (const item of items) {\n\t\tif ((item.type === \"page\" || item.type === \"post\") && item.ref) {\n\t\t\tif (!contentIds.has(item.ref)) {\n\t\t\t\twarnings.push(`Menu item references content \"${item.ref}\" which is not in the seed file`);\n\t\t\t}\n\t\t}\n\n\t\tif (item.children) {\n\t\t\tvalidateMenuItemRefs(item.children, contentIds, warnings);\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -1,7 +0,0 @@
1
- //#region src/version.ts
2
- const VERSION = "0.18.0";
3
- const COMMIT = "849628ff";
4
-
5
- //#endregion
6
- export { VERSION as n, COMMIT as t };
7
- //# sourceMappingURL=version-CnS-Cr8A.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"widgets-Bap1eS1X.mjs","names":["getComponentRegistry"],"sources":["../src/widgets/index.ts"],"sourcesContent":["import { getDb } from \"../loader.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\tconst db = await getDb();\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_widget_areas as a\")\n\t\t.leftJoin(\"_emdash_widgets as w\", \"w.area_id\", \"a.id\")\n\t\t.select([\n\t\t\t\"a.id as a_id\",\n\t\t\t\"a.name as a_name\",\n\t\t\t\"a.label as a_label\",\n\t\t\t\"a.description as a_description\",\n\t\t\t\"w.id as w_id\",\n\t\t\t\"w.type as w_type\",\n\t\t\t\"w.title as w_title\",\n\t\t\t\"w.content as w_content\",\n\t\t\t\"w.menu_name as w_menu_name\",\n\t\t\t\"w.component_id as w_component_id\",\n\t\t\t\"w.component_props as w_component_props\",\n\t\t\t\"w.area_id as w_area_id\",\n\t\t\t\"w.sort_order as w_sort_order\",\n\t\t\t\"w.created_at as w_created_at\",\n\t\t])\n\t\t.where(\"a.name\", \"=\", name)\n\t\t.orderBy(\"w.sort_order\", \"asc\")\n\t\t.execute();\n\n\tconst first = rows[0];\n\tif (!first) return null;\n\tconst widgets: Widget[] = [];\n\tfor (const row of rows) {\n\t\tif (row.w_id === null) continue; // area has no widgets (left-join null row)\n\t\t// Left-join makes every w_* column nullable in the type; at runtime\n\t\t// they're all non-null once w_id is (we match on widgets.area_id, so\n\t\t// a widget row always has the not-null columns filled). Cast is the\n\t\t// price of that structural fact.\n\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\tconst widgetRow = {\n\t\t\tid: row.w_id,\n\t\t\ttype: row.w_type,\n\t\t\ttitle: row.w_title,\n\t\t\tcontent: row.w_content,\n\t\t\tmenu_name: row.w_menu_name,\n\t\t\tcomponent_id: row.w_component_id,\n\t\t\tcomponent_props: row.w_component_props,\n\t\t\tarea_id: row.w_area_id,\n\t\t\tsort_order: row.w_sort_order,\n\t\t\tcreated_at: row.w_created_at,\n\t\t} as WidgetRow;\n\t\twidgets.push(rowToWidget(widgetRow));\n\t}\n\n\treturn {\n\t\tid: first.a_id,\n\t\tname: first.a_name,\n\t\tlabel: first.a_label,\n\t\tdescription: first.a_description ?? undefined,\n\t\twidgets,\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":";;;;;;;;;;;AAuBA,eAAsB,cAAc,MAA0C;CAE7E,MAAM,OAAO,OADF,MAAM,OAAO,EAEtB,WAAW,4BAA4B,CACvC,SAAS,wBAAwB,aAAa,OAAO,CACrD,OAAO;EACP;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,CAAC,CACD,MAAM,UAAU,KAAK,KAAK,CAC1B,QAAQ,gBAAgB,MAAM,CAC9B,SAAS;CAEX,MAAM,QAAQ,KAAK;AACnB,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,OAAO,MAAM;AACvB,MAAI,IAAI,SAAS,KAAM;EAMvB,MAAM,YAAY;GACjB,IAAI,IAAI;GACR,MAAM,IAAI;GACV,OAAO,IAAI;GACX,SAAS,IAAI;GACb,WAAW,IAAI;GACf,cAAc,IAAI;GAClB,iBAAiB,IAAI;GACrB,SAAS,IAAI;GACb,YAAY,IAAI;GAChB,YAAY,IAAI;GAChB;AACD,UAAQ,KAAK,YAAY,UAAU,CAAC;;AAGrC,QAAO;EACN,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,OAAO,MAAM;EACb,aAAa,MAAM,iBAAiB;EACpC;EACA;;;;;AAMF,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"}