emdash 0.34.0 → 0.36.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 (800) hide show
  1. package/dist/activation-K0kGBWuJ.mjs +382 -0
  2. package/dist/activation-K0kGBWuJ.mjs.map +1 -0
  3. package/dist/{adapters-34EN2eXO.d.mts → adapters-CzL3H752.d.mts} +10 -3
  4. package/dist/adapters-CzL3H752.d.mts.map +1 -0
  5. package/dist/{allowed-origins-DW7sYyqg.mjs → allowed-origins-maylGaHv.mjs} +2 -2
  6. package/dist/{allowed-origins-DW7sYyqg.mjs.map → allowed-origins-maylGaHv.mjs.map} +1 -1
  7. package/dist/api/route-utils.d.mts +3 -3
  8. package/dist/api/route-utils.mjs +22 -20
  9. package/dist/api/route-utils.mjs.map +1 -1
  10. package/dist/api/schemas/index.d.mts +2 -2
  11. package/dist/api/schemas/index.mjs +5 -5
  12. package/dist/api-B0w3uBiG.mjs +2269 -0
  13. package/dist/api-B0w3uBiG.mjs.map +1 -0
  14. package/dist/{api-tokens-CFXu8F_2.mjs → api-tokens-9YrrCoE6.mjs} +2 -2
  15. package/dist/{api-tokens-CFXu8F_2.mjs.map → api-tokens-9YrrCoE6.mjs.map} +1 -1
  16. package/dist/{apply-D5zfeD09.mjs → apply-B_VMpJ2P.mjs} +34 -35
  17. package/dist/{apply-D5zfeD09.mjs.map → apply-B_VMpJ2P.mjs.map} +1 -1
  18. package/dist/astro/index.d.mts +12 -12
  19. package/dist/astro/index.d.mts.map +1 -1
  20. package/dist/astro/index.mjs +92 -25
  21. package/dist/astro/index.mjs.map +1 -1
  22. package/dist/astro/middleware/auth.d.mts +10 -10
  23. package/dist/astro/middleware/auth.d.mts.map +1 -1
  24. package/dist/astro/middleware/auth.mjs +10 -7
  25. package/dist/astro/middleware/auth.mjs.map +1 -1
  26. package/dist/astro/middleware/media-usage-write-fence.mjs +2 -2
  27. package/dist/astro/middleware/redirect.mjs +4 -4
  28. package/dist/astro/middleware/request-context.mjs +3 -3
  29. package/dist/astro/middleware/setup.mjs +1 -1
  30. package/dist/astro/middleware.d.mts +11 -12
  31. package/dist/astro/middleware.d.mts.map +1 -1
  32. package/dist/astro/middleware.mjs +255 -1323
  33. package/dist/astro/middleware.mjs.map +1 -1
  34. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  35. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  36. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  37. package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  39. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -9
  40. package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -9
  41. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -9
  42. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +15 -15
  43. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +15 -15
  44. package/dist/astro/routes/api/admin/bylines/index.mjs +15 -15
  45. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +12 -12
  46. package/dist/astro/routes/api/admin/comments/_id_.mjs +4 -4
  47. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  48. package/dist/astro/routes/api/admin/comments/counts.mjs +4 -4
  49. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  50. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  51. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  52. package/dist/astro/routes/api/admin/media-usage/activation.d.mts +9 -0
  53. package/dist/astro/routes/api/admin/media-usage/activation.d.mts.map +1 -0
  54. package/dist/astro/routes/api/admin/media-usage/activation.mjs +46 -0
  55. package/dist/astro/routes/api/admin/media-usage/activation.mjs.map +1 -0
  56. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs +14 -13
  57. package/dist/astro/routes/api/admin/media-usage/collection-deletions/index.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs +14 -13
  59. package/dist/astro/routes/api/admin/media-usage/collection-deletions/retry.mjs.map +1 -1
  60. package/dist/astro/routes/api/admin/media-usage/progress.d.mts +9 -0
  61. package/dist/astro/routes/api/admin/media-usage/progress.d.mts.map +1 -0
  62. package/dist/astro/routes/api/admin/media-usage/progress.mjs +44 -0
  63. package/dist/astro/routes/api/admin/media-usage/progress.mjs.map +1 -0
  64. package/dist/astro/routes/api/admin/media-usage/repair.mjs +16 -11
  65. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/media-usage/work/index.mjs +14 -13
  67. package/dist/astro/routes/api/admin/media-usage/work/index.mjs.map +1 -1
  68. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs +14 -13
  69. package/dist/astro/routes/api/admin/media-usage/work/retry.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  71. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  72. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +54 -48
  73. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +54 -48
  75. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  76. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +52 -46
  77. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  79. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +52 -46
  80. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +53 -47
  82. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +53 -47
  84. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  85. package/dist/astro/routes/api/admin/plugins/index.mjs +52 -46
  86. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  88. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +52 -46
  89. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +53 -47
  91. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +52 -46
  93. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  94. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +53 -47
  95. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +54 -48
  97. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  98. package/dist/astro/routes/api/admin/plugins/registry/artifact.d.mts.map +1 -1
  99. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +172 -133
  100. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  101. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +54 -48
  102. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  103. package/dist/astro/routes/api/admin/plugins/updates.mjs +52 -46
  104. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  105. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +52 -46
  106. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  107. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  108. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +52 -46
  109. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  110. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  111. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  112. package/dist/astro/routes/api/admin/users/_id_/index.mjs +7 -7
  113. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
  114. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  115. package/dist/astro/routes/api/auth/dev-bypass.d.mts.map +1 -1
  116. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -7
  117. package/dist/astro/routes/api/auth/dev-bypass.mjs.map +1 -1
  118. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  119. package/dist/astro/routes/api/auth/invite/complete.mjs +11 -11
  120. package/dist/astro/routes/api/auth/invite/index.mjs +8 -8
  121. package/dist/astro/routes/api/auth/invite/register-options.mjs +10 -10
  122. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  123. package/dist/astro/routes/api/auth/magic-link/send.mjs +10 -10
  124. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  125. package/dist/astro/routes/api/auth/me.d.mts.map +1 -1
  126. package/dist/astro/routes/api/auth/me.mjs +19 -9
  127. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  128. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  129. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  130. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  131. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  132. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  133. package/dist/astro/routes/api/auth/passkey/options.mjs +12 -12
  134. package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -10
  135. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -11
  136. package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -11
  137. package/dist/astro/routes/api/auth/signup/complete.mjs +11 -11
  138. package/dist/astro/routes/api/auth/signup/request.mjs +10 -10
  139. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  140. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +13 -13
  141. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +12 -12
  142. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  143. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  144. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  145. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  146. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -11
  147. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +8 -8
  148. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +16 -14
  149. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
  150. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +16 -14
  151. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
  152. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  153. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  154. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  155. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  156. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  157. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  158. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  159. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  160. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  161. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  162. package/dist/astro/routes/api/dashboard.mjs +7 -7
  163. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  164. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  165. package/dist/astro/routes/api/import/probe.mjs +16 -16
  166. package/dist/astro/routes/api/import/wordpress/analyze.mjs +2 -2
  167. package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
  168. package/dist/astro/routes/api/import/wordpress/execute.mjs +12 -12
  169. package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
  170. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
  171. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  172. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
  173. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +12 -10
  174. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  175. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  176. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +16 -16
  177. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
  178. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +26 -26
  179. package/dist/astro/routes/api/manifest.mjs +4 -4
  180. package/dist/astro/routes/api/mcp.mjs +42 -42
  181. package/dist/astro/routes/api/media/_id_/confirm.mjs +11 -11
  182. package/dist/astro/routes/api/media/_id_/upload.mjs +6 -6
  183. package/dist/astro/routes/api/media/_id_/usage.mjs +18 -13
  184. package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
  185. package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
  186. package/dist/astro/routes/api/media/_id_.mjs +21 -13
  187. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  188. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  189. package/dist/astro/routes/api/media/folders/_id_.d.mts +10 -0
  190. package/dist/astro/routes/api/media/folders/_id_.d.mts.map +1 -0
  191. package/dist/astro/routes/api/media/folders/_id_.mjs +52 -0
  192. package/dist/astro/routes/api/media/folders/_id_.mjs.map +1 -0
  193. package/dist/astro/routes/api/media/folders/index.d.mts +9 -0
  194. package/dist/astro/routes/api/media/folders/index.d.mts.map +1 -0
  195. package/dist/astro/routes/api/media/folders/index.mjs +41 -0
  196. package/dist/astro/routes/api/media/folders/index.mjs.map +1 -0
  197. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  198. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  199. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  200. package/dist/astro/routes/api/media/upload-url.mjs +9 -9
  201. package/dist/astro/routes/api/media.d.mts.map +1 -1
  202. package/dist/astro/routes/api/media.mjs +28 -19
  203. package/dist/astro/routes/api/media.mjs.map +1 -1
  204. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  205. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  206. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  207. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  208. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  209. package/dist/astro/routes/api/menus/index.mjs +8 -8
  210. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  211. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  212. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  213. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  214. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  215. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  216. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  217. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  218. package/dist/astro/routes/api/openapi.json.mjs +153 -6
  219. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  220. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  221. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  222. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  223. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  224. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  225. package/dist/astro/routes/api/relations/_id_/index.mjs +16 -14
  226. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
  227. package/dist/astro/routes/api/relations/_id_/translations.mjs +11 -9
  228. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
  229. package/dist/astro/routes/api/relations/index.mjs +16 -14
  230. package/dist/astro/routes/api/relations/index.mjs.map +1 -1
  231. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  232. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  233. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +52 -46
  234. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  235. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +52 -46
  236. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  237. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +52 -46
  238. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  239. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +52 -46
  240. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  241. package/dist/astro/routes/api/schema/collections/index.mjs +52 -46
  242. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  243. package/dist/astro/routes/api/schema/collections/reorder.mjs +52 -46
  244. package/dist/astro/routes/api/schema/collections/reorder.mjs.map +1 -1
  245. package/dist/astro/routes/api/schema/index.mjs +10 -8
  246. package/dist/astro/routes/api/schema/index.mjs.map +1 -1
  247. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +52 -46
  248. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  249. package/dist/astro/routes/api/schema/orphans/index.mjs +52 -46
  250. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  251. package/dist/astro/routes/api/search/enable.mjs +10 -10
  252. package/dist/astro/routes/api/search/index.mjs +9 -9
  253. package/dist/astro/routes/api/search/rebuild.mjs +10 -10
  254. package/dist/astro/routes/api/search/stats.mjs +5 -5
  255. package/dist/astro/routes/api/search/suggest.mjs +9 -9
  256. package/dist/astro/routes/api/sections/_slug_.mjs +9 -9
  257. package/dist/astro/routes/api/sections/index.mjs +9 -9
  258. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +6 -6
  259. package/dist/astro/routes/api/settings/backups/archives/index.mjs +5 -5
  260. package/dist/astro/routes/api/settings/backups/export.mjs +6 -6
  261. package/dist/astro/routes/api/settings/backups/index.mjs +7 -7
  262. package/dist/astro/routes/api/settings/email.mjs +4 -4
  263. package/dist/astro/routes/api/settings.mjs +13 -13
  264. package/dist/astro/routes/api/setup/admin-verify.mjs +12 -12
  265. package/dist/astro/routes/api/setup/admin.mjs +11 -11
  266. package/dist/astro/routes/api/setup/dev-bypass.d.mts.map +1 -1
  267. package/dist/astro/routes/api/setup/dev-bypass.mjs +35 -31
  268. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  269. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  270. package/dist/astro/routes/api/setup/index.d.mts.map +1 -1
  271. package/dist/astro/routes/api/setup/index.mjs +31 -35
  272. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  273. package/dist/astro/routes/api/setup/status.mjs +3 -3
  274. package/dist/astro/routes/api/snapshot.mjs +7 -7
  275. package/dist/astro/routes/api/taxonomies/_name_/reorder.mjs +13 -13
  276. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  277. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  278. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  279. package/dist/astro/routes/api/taxonomies/_name_/translations.mjs +13 -13
  280. package/dist/astro/routes/api/taxonomies/_name_.mjs +13 -13
  281. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  282. package/dist/astro/routes/api/themes/preview.mjs +5 -5
  283. package/dist/astro/routes/api/typegen.mjs +4 -4
  284. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  285. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  286. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  287. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  288. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
  289. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
  290. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  291. package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
  292. package/dist/astro/routes/api/widget-components.mjs +2 -2
  293. package/dist/astro/routes/robots.txt.mjs +6 -6
  294. package/dist/astro/routes/sitemap-_collection_.xml.mjs +9 -9
  295. package/dist/astro/routes/sitemap.xml.mjs +7 -7
  296. package/dist/astro/types.d.mts +19 -14
  297. package/dist/astro/types.d.mts.map +1 -1
  298. package/dist/auth/providers/github.d.mts +1 -1
  299. package/dist/auth/providers/google.d.mts +1 -1
  300. package/dist/{authorize-1iFJzPaZ.mjs → authorize-ByYs-Uxs.mjs} +2 -2
  301. package/dist/{authorize-1iFJzPaZ.mjs.map → authorize-ByYs-Uxs.mjs.map} +1 -1
  302. package/dist/{backup-LHjIAVNP.mjs → backup-ChyHfIoX.mjs} +5 -5
  303. package/dist/{backup-LHjIAVNP.mjs.map → backup-ChyHfIoX.mjs.map} +1 -1
  304. package/dist/{bundle-CBEXrBnb.mjs → bundle-DZFhFurR.mjs} +7 -7
  305. package/dist/{bundle-CBEXrBnb.mjs.map → bundle-DZFhFurR.mjs.map} +1 -1
  306. package/dist/{byline-pEcoDps8.mjs → byline-Cpnwrje1.mjs} +6 -6
  307. package/dist/{byline-pEcoDps8.mjs.map → byline-Cpnwrje1.mjs.map} +1 -1
  308. package/dist/{byline-fields-C5S8wygO.mjs → byline-fields-CAyA_Umd.mjs} +2 -2
  309. package/dist/{byline-fields-C5S8wygO.mjs.map → byline-fields-CAyA_Umd.mjs.map} +1 -1
  310. package/dist/{byline-registry-DDun8sxh.mjs → byline-registry-Bpu9-MVs.mjs} +2 -2
  311. package/dist/{byline-registry-DDun8sxh.mjs.map → byline-registry-Bpu9-MVs.mjs.map} +1 -1
  312. package/dist/{bylines-BWemdX2k.mjs → bylines-BSo9B9RX.mjs} +7 -7
  313. package/dist/{bylines-BWemdX2k.mjs.map → bylines-BSo9B9RX.mjs.map} +1 -1
  314. package/dist/{bylines-DJfSfXCL.mjs → bylines-CHjvehVX.mjs} +3 -3
  315. package/dist/{bylines-DJfSfXCL.mjs.map → bylines-CHjvehVX.mjs.map} +1 -1
  316. package/dist/{cache-CBJaDy_E.mjs → cache-5VirJ2Cn.mjs} +4 -4
  317. package/dist/{cache-CBJaDy_E.mjs.map → cache-5VirJ2Cn.mjs.map} +1 -1
  318. package/dist/capture-triggers-CNW1q3HK.mjs +362 -0
  319. package/dist/capture-triggers-CNW1q3HK.mjs.map +1 -0
  320. package/dist/{challenge-store-CZyFVaad.mjs → challenge-store-D5pSY2S1.mjs} +1 -1
  321. package/dist/{challenge-store-CZyFVaad.mjs.map → challenge-store-D5pSY2S1.mjs.map} +1 -1
  322. package/dist/{chunks-DfPax1I0.mjs → chunks-bNiX6_DR.mjs} +2 -2
  323. package/dist/{chunks-DfPax1I0.mjs.map → chunks-bNiX6_DR.mjs.map} +1 -1
  324. package/dist/cli/index.mjs +568 -84
  325. package/dist/cli/index.mjs.map +1 -1
  326. package/dist/client/cf-access.d.mts +1 -1
  327. package/dist/client/index.d.mts +74 -4
  328. package/dist/client/index.d.mts.map +1 -1
  329. package/dist/client/index.mjs +54 -2
  330. package/dist/client/index.mjs.map +1 -1
  331. package/dist/{collection-deletion-C8BugSGz.mjs → collection-deletion-m_oi2c0N.mjs} +13 -356
  332. package/dist/collection-deletion-m_oi2c0N.mjs.map +1 -0
  333. package/dist/{comment-Dxlep6cE.mjs → comment-DhvLEdhQ.mjs} +1 -1
  334. package/dist/{comment-Dxlep6cE.mjs.map → comment-DhvLEdhQ.mjs.map} +1 -1
  335. package/dist/{comment-reaction-D6qfHKBc.mjs → comment-reaction-CRuItEjt.mjs} +2 -2
  336. package/dist/{comment-reaction-D6qfHKBc.mjs.map → comment-reaction-CRuItEjt.mjs.map} +1 -1
  337. package/dist/{comments-CTOuhTSX.mjs → comments-CLj4bi8I.mjs} +2 -2
  338. package/dist/{comments-CTOuhTSX.mjs.map → comments-CLj4bi8I.mjs.map} +1 -1
  339. package/dist/{components-CQy3lh9x.mjs → components-v4U4DT2y.mjs} +1 -1
  340. package/dist/{components-CQy3lh9x.mjs.map → components-v4U4DT2y.mjs.map} +1 -1
  341. package/dist/config-DrfghTPe.d.mts +34 -0
  342. package/dist/config-DrfghTPe.d.mts.map +1 -0
  343. package/dist/{content-BNfwh7xX.mjs → content-CP-SeXNa.mjs} +5 -4
  344. package/dist/content-CP-SeXNa.mjs.map +1 -0
  345. package/dist/{content-list-query-DhQ84eVV.d.mts → content-list-query-BhejKVqb.d.mts} +1 -1
  346. package/dist/{content-list-query-DhQ84eVV.d.mts.map → content-list-query-BhejKVqb.d.mts.map} +1 -1
  347. package/dist/{content-refresh-DNERP4-f.mjs → content-refresh-DLOfIhYc.mjs} +1094 -93
  348. package/dist/content-refresh-DLOfIhYc.mjs.map +1 -0
  349. package/dist/{media-usage-De3qvwjc.mjs → content-repair-BCxzy0r4.mjs} +5 -196
  350. package/dist/content-repair-BCxzy0r4.mjs.map +1 -0
  351. package/dist/{context-DRgOvSnW.mjs → context-BR5hRWiK.mjs} +13 -13
  352. package/dist/{context-DRgOvSnW.mjs.map → context-BR5hRWiK.mjs.map} +1 -1
  353. package/dist/{cron-Y9aLtgrA.mjs → cron-CWfWrWKG.mjs} +1 -1
  354. package/dist/{cron-Y9aLtgrA.mjs.map → cron-CWfWrWKG.mjs.map} +1 -1
  355. package/dist/{dashboard-CB8J89rJ.mjs → dashboard-CkIV2rnQ.mjs} +5 -5
  356. package/dist/{dashboard-CB8J89rJ.mjs.map → dashboard-CkIV2rnQ.mjs.map} +1 -1
  357. package/dist/db/index.d.mts +4 -4
  358. package/dist/db/index.mjs +32 -9
  359. package/dist/db/index.mjs.map +1 -1
  360. package/dist/db/libsql-migrations.d.mts +11 -0
  361. package/dist/db/libsql-migrations.d.mts.map +1 -0
  362. package/dist/db/libsql-migrations.mjs +57 -0
  363. package/dist/db/libsql-migrations.mjs.map +1 -0
  364. package/dist/db/libsql.d.mts +1 -1
  365. package/dist/db/postgres-migrations.d.mts +10 -0
  366. package/dist/db/postgres-migrations.d.mts.map +1 -0
  367. package/dist/db/postgres-migrations.mjs +47 -0
  368. package/dist/db/postgres-migrations.mjs.map +1 -0
  369. package/dist/db/postgres.d.mts +1 -1
  370. package/dist/db/sqlite-migrations.d.mts +10 -0
  371. package/dist/db/sqlite-migrations.d.mts.map +1 -0
  372. package/dist/db/sqlite-migrations.mjs +28 -0
  373. package/dist/db/sqlite-migrations.mjs.map +1 -0
  374. package/dist/db/sqlite.d.mts +1 -1
  375. package/dist/db/sqlite.mjs +3 -3
  376. package/dist/db/sqlite.mjs.map +1 -1
  377. package/dist/{default-DdDl3fxv.mjs → default-YMEfcQUx.mjs} +1 -1
  378. package/dist/{default-DdDl3fxv.mjs.map → default-YMEfcQUx.mjs.map} +1 -1
  379. package/dist/{deferred-tasks-BMsa9F64.d.mts → deferred-tasks-DfqxH5Xf.d.mts} +1 -1
  380. package/dist/{deferred-tasks-BMsa9F64.d.mts.map → deferred-tasks-DfqxH5Xf.d.mts.map} +1 -1
  381. package/dist/{device-flow-CaioYGK3.mjs → device-flow-CojjSBwb.mjs} +5 -5
  382. package/dist/{device-flow-CaioYGK3.mjs.map → device-flow-CojjSBwb.mjs.map} +1 -1
  383. package/dist/direct-executor-BWeWiRYB.mjs +85 -0
  384. package/dist/direct-executor-BWeWiRYB.mjs.map +1 -0
  385. package/dist/{dist-D_6CKR8z.mjs → dist-89xCrGqi.mjs} +1 -1
  386. package/dist/{dist-D_6CKR8z.mjs.map → dist-89xCrGqi.mjs.map} +1 -1
  387. package/dist/{email-console-BHNjIwiB.mjs → email-console-CEJgR_LB.mjs} +1 -1
  388. package/dist/{email-console-BHNjIwiB.mjs.map → email-console-CEJgR_LB.mjs.map} +1 -1
  389. package/dist/{enrich-_ySMEvEE.mjs → enrich-BX0IDp4z.mjs} +3 -3
  390. package/dist/{enrich-_ySMEvEE.mjs.map → enrich-BX0IDp4z.mjs.map} +1 -1
  391. package/dist/{error-DupyJZE2.mjs → error-D9w94fjV.mjs} +2 -2
  392. package/dist/{error-DupyJZE2.mjs.map → error-D9w94fjV.mjs.map} +1 -1
  393. package/dist/{errors-CLUPm7aD.mjs → errors-n2j49uNf.mjs} +22 -2
  394. package/dist/errors-n2j49uNf.mjs.map +1 -0
  395. package/dist/{escape-C_aLKP3H.mjs → escape-DN4Qmjsl.mjs} +1 -1
  396. package/dist/{escape-C_aLKP3H.mjs.map → escape-DN4Qmjsl.mjs.map} +1 -1
  397. package/dist/{field-defs-cache-hg6QkjV9.mjs → field-defs-cache-Bh4q6oar.mjs} +5 -5
  398. package/dist/{field-defs-cache-hg6QkjV9.mjs.map → field-defs-cache-Bh4q6oar.mjs.map} +1 -1
  399. package/dist/focal-point-BeWFo6Xx.mjs +23 -0
  400. package/dist/focal-point-BeWFo6Xx.mjs.map +1 -0
  401. package/dist/{fts-manager-CZ-5dVwJ.mjs → fts-manager-BhOdW0bi.mjs} +3 -3
  402. package/dist/{fts-manager-CZ-5dVwJ.mjs.map → fts-manager-BhOdW0bi.mjs.map} +1 -1
  403. package/dist/{hash-Cg0TXJPV.mjs → hash-CTcngWvj.mjs} +1 -1
  404. package/dist/{hash-Cg0TXJPV.mjs.map → hash-CTcngWvj.mjs.map} +1 -1
  405. package/dist/{hreflang-D3Uehfv2.d.mts → hreflang-7pTHsLZJ.d.mts} +2 -2
  406. package/dist/{hreflang-D3Uehfv2.d.mts.map → hreflang-7pTHsLZJ.d.mts.map} +1 -1
  407. package/dist/{hreflang-DNswoYsi.mjs → hreflang-Huc6O5wa.mjs} +7 -7
  408. package/dist/{hreflang-DNswoYsi.mjs.map → hreflang-Huc6O5wa.mjs.map} +1 -1
  409. package/dist/identity-DXmb4aVp.mjs +35 -0
  410. package/dist/identity-DXmb4aVp.mjs.map +1 -0
  411. package/dist/{import-B41u8Rot.mjs → import-C4_O3KXU.mjs} +5 -5
  412. package/dist/{import-B41u8Rot.mjs.map → import-C4_O3KXU.mjs.map} +1 -1
  413. package/dist/{index-Cof4-Fab.d.mts → index-6bRee8zb.d.mts} +3 -3
  414. package/dist/{index-Cof4-Fab.d.mts.map → index-6bRee8zb.d.mts.map} +1 -1
  415. package/dist/{index-C3Y-rsyN.d.mts → index-DWbJrsAg.d.mts} +138 -83
  416. package/dist/index-DWbJrsAg.d.mts.map +1 -0
  417. package/dist/index.d.mts +21 -20
  418. package/dist/index.mjs +78 -72
  419. package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
  420. package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
  421. package/dist/{load-DmvJrtxS.mjs → load-Bo7fZAbg.mjs} +2 -2
  422. package/dist/{load-DmvJrtxS.mjs.map → load-Bo7fZAbg.mjs.map} +1 -1
  423. package/dist/{loader-DLBqRiGP.mjs → loader-ZsjE4paL.mjs} +2 -2
  424. package/dist/{loader-DLBqRiGP.mjs.map → loader-ZsjE4paL.mjs.map} +1 -1
  425. package/dist/manifest-builder-QXuqvVMP.mjs +189 -0
  426. package/dist/manifest-builder-QXuqvVMP.mjs.map +1 -0
  427. package/dist/{manifest-schema-BXbUAHod.mjs → manifest-schema-CUYPfCEH.mjs} +1 -1
  428. package/dist/{manifest-schema-BXbUAHod.mjs.map → manifest-schema-CUYPfCEH.mjs.map} +1 -1
  429. package/dist/media/image-endpoint.mjs +1 -1
  430. package/dist/media/index.d.mts +1 -1
  431. package/dist/media/index.mjs +13 -3
  432. package/dist/media/index.mjs.map +1 -1
  433. package/dist/media/local-runtime.d.mts +14 -12
  434. package/dist/media/local-runtime.d.mts.map +1 -1
  435. package/dist/media/local-runtime.mjs +11 -5
  436. package/dist/media/local-runtime.mjs.map +1 -1
  437. package/dist/{media-BWWWb8kg.mjs → media-BjwNuLEu.mjs} +100 -6
  438. package/dist/media-BjwNuLEu.mjs.map +1 -0
  439. package/dist/{media-CzhAn2--.mjs → media-D6SeAfBl.mjs} +41 -12
  440. package/dist/media-D6SeAfBl.mjs.map +1 -0
  441. package/dist/{media-allowlist-BD6HhgXA.mjs → media-allowlist-D35v7bmL.mjs} +3 -2
  442. package/dist/media-allowlist-D35v7bmL.mjs.map +1 -0
  443. package/dist/media-folders-D-0CmRZj.mjs +210 -0
  444. package/dist/media-folders-D-0CmRZj.mjs.map +1 -0
  445. package/dist/{media-upload-9yas9zGH.mjs → media-upload-C7bLXGcI.mjs} +9 -9
  446. package/dist/{media-upload-9yas9zGH.mjs.map → media-upload-C7bLXGcI.mjs.map} +1 -1
  447. package/dist/{media-url-BvoX88FA.mjs → media-url-DH7giMx_.mjs} +1 -1
  448. package/dist/{media-url-BvoX88FA.mjs.map → media-url-DH7giMx_.mjs.map} +1 -1
  449. package/dist/media-usage-BTRJvS6V.mjs +1833 -0
  450. package/dist/media-usage-BTRJvS6V.mjs.map +1 -0
  451. package/dist/media-usage-activation-Dx0kPV6b.mjs +78 -0
  452. package/dist/media-usage-activation-Dx0kPV6b.mjs.map +1 -0
  453. package/dist/{media-usage-work-DrCbqBcl.mjs → media-usage-work-C-fhM30c.mjs} +221 -59
  454. package/dist/media-usage-work-C-fhM30c.mjs.map +1 -0
  455. package/dist/{media-usage-work-B9Bksh_b.mjs → media-usage-work-DWb2XPMI.mjs} +4 -4
  456. package/dist/{media-usage-work-B9Bksh_b.mjs.map → media-usage-work-DWb2XPMI.mjs.map} +1 -1
  457. package/dist/{media-usage-write-fence-aHEgN1zB.mjs → media-usage-write-fence-BwoJVRDC.mjs} +2 -2
  458. package/dist/{media-usage-write-fence-aHEgN1zB.mjs.map → media-usage-write-fence-BwoJVRDC.mjs.map} +1 -1
  459. package/dist/{menus-fxvEDYE6.mjs → menus-CD7EFLwz.mjs} +3 -3
  460. package/dist/{menus-fxvEDYE6.mjs.map → menus-CD7EFLwz.mjs.map} +1 -1
  461. package/dist/{menus-CC-yn1F9.mjs → menus-CNg0QlDt.mjs} +42 -26
  462. package/dist/menus-CNg0QlDt.mjs.map +1 -0
  463. package/dist/migrations/index.d.mts +22 -0
  464. package/dist/migrations/index.d.mts.map +1 -0
  465. package/dist/migrations/index.mjs +7 -0
  466. package/dist/{mime-DfIVjNkr.mjs → mime-CUIG6x84.mjs} +1 -1
  467. package/dist/mime-CUIG6x84.mjs.map +1 -0
  468. package/dist/{mode-Bpe-tNNy.mjs → mode-DaUM_MB2.mjs} +1 -1
  469. package/dist/{mode-Bpe-tNNy.mjs.map → mode-DaUM_MB2.mjs.map} +1 -1
  470. package/dist/node-sqlite-compat-Udb4Y-rs.mjs +46 -0
  471. package/dist/node-sqlite-compat-Udb4Y-rs.mjs.map +1 -0
  472. package/dist/{normalize-Bc-s_r-a.mjs → normalize-CSxdKBVa.mjs} +22 -1
  473. package/dist/normalize-CSxdKBVa.mjs.map +1 -0
  474. package/dist/{oauth-authorization-BXO1MKej.mjs → oauth-authorization-Dpsb-fMJ.mjs} +5 -5
  475. package/dist/{oauth-authorization-BXO1MKej.mjs.map → oauth-authorization-Dpsb-fMJ.mjs.map} +1 -1
  476. package/dist/{oauth-clients-C_DNqtRu.mjs → oauth-clients-Bxnoe_67.mjs} +1 -1
  477. package/dist/{oauth-clients-C_DNqtRu.mjs.map → oauth-clients-Bxnoe_67.mjs.map} +1 -1
  478. package/dist/{oauth-state-store-DwAxMtk4.mjs → oauth-state-store-0C4s0K6w.mjs} +1 -1
  479. package/dist/{oauth-state-store-DwAxMtk4.mjs.map → oauth-state-store-0C4s0K6w.mjs.map} +1 -1
  480. package/dist/{oauth-user-lookup-BxfZizdk.mjs → oauth-user-lookup-wlGYSeqX.mjs} +1 -1
  481. package/dist/{oauth-user-lookup-BxfZizdk.mjs.map → oauth-user-lookup-wlGYSeqX.mjs.map} +1 -1
  482. package/dist/object-cache/memory.d.mts +1 -1
  483. package/dist/{options-CBXIHUoT.mjs → options-BHPeIbBd.mjs} +1 -1
  484. package/dist/{options-CBXIHUoT.mjs.map → options-BHPeIbBd.mjs.map} +1 -1
  485. package/dist/{options-DRe1pIBL.d.mts → options-BhZIeFY1.d.mts} +3 -3
  486. package/dist/{options-DRe1pIBL.d.mts.map → options-BhZIeFY1.d.mts.map} +1 -1
  487. package/dist/page/index.d.mts +2 -2
  488. package/dist/{parse-DFbuc-Fh.mjs → parse-BRJg1kz8.mjs} +2 -2
  489. package/dist/{parse-DFbuc-Fh.mjs.map → parse-BRJg1kz8.mjs.map} +1 -1
  490. package/dist/{passkey-config-_2GOwwK8.mjs → passkey-config-B3P0nHRR.mjs} +1 -1
  491. package/dist/{passkey-config-_2GOwwK8.mjs.map → passkey-config-B3P0nHRR.mjs.map} +1 -1
  492. package/dist/{patterns-CiyXeDgr.mjs → patterns-D9iDO3j8.mjs} +1 -1
  493. package/dist/{patterns-CiyXeDgr.mjs.map → patterns-D9iDO3j8.mjs.map} +1 -1
  494. package/dist/{placeholder-CzW7Lshs.d.mts → placeholder-BR4ZhVJl.d.mts} +7 -1
  495. package/dist/placeholder-BR4ZhVJl.d.mts.map +1 -0
  496. package/dist/plugin-types.d.mts +1 -1
  497. package/dist/plugin-utils.d.mts +10 -10
  498. package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
  499. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  500. package/dist/policy-BVRganJQ.mjs +45 -0
  501. package/dist/policy-BVRganJQ.mjs.map +1 -0
  502. package/dist/{portable-text-DhKbDAGr.mjs → portable-text-C8Tvp4_g.mjs} +1 -1
  503. package/dist/{portable-text-DhKbDAGr.mjs.map → portable-text-C8Tvp4_g.mjs.map} +1 -1
  504. package/dist/{preview-PW-nOnq4.mjs → preview-dn2dUyM0.mjs} +2 -2
  505. package/dist/{preview-PW-nOnq4.mjs.map → preview-dn2dUyM0.mjs.map} +1 -1
  506. package/dist/protocol-CuBEsqgE.d.mts +52 -0
  507. package/dist/protocol-CuBEsqgE.d.mts.map +1 -0
  508. package/dist/{public-url-CWoFhQT8.mjs → public-url-DRz_AinV.mjs} +1 -1
  509. package/dist/{public-url-CWoFhQT8.mjs.map → public-url-DRz_AinV.mjs.map} +1 -1
  510. package/dist/{query-BgjfiLUQ.mjs → query-D18vTdha.mjs} +14 -14
  511. package/dist/{query-BgjfiLUQ.mjs.map → query-D18vTdha.mjs.map} +1 -1
  512. package/dist/{query-vgZNwIhh.mjs → query-tZDEdtCN.mjs} +4 -4
  513. package/dist/{query-vgZNwIhh.mjs.map → query-tZDEdtCN.mjs.map} +1 -1
  514. package/dist/{rate-limit-L5anykgo.mjs → rate-limit-on7KPo-v.mjs} +2 -2
  515. package/dist/{rate-limit-L5anykgo.mjs.map → rate-limit-on7KPo-v.mjs.map} +1 -1
  516. package/dist/{redirect-COsS6SWJ.mjs → redirect-CZkbozu9.mjs} +2 -2
  517. package/dist/{redirect-COsS6SWJ.mjs.map → redirect-CZkbozu9.mjs.map} +1 -1
  518. package/dist/{redirect-KBNE3UEs.mjs → redirect-DrbEmU9a.mjs} +1 -1
  519. package/dist/{redirect-KBNE3UEs.mjs.map → redirect-DrbEmU9a.mjs.map} +1 -1
  520. package/dist/{redirects-CufRQt0Q.mjs → redirects-BahWiu8Z.mjs} +5 -5
  521. package/dist/{redirects-CufRQt0Q.mjs.map → redirects-BahWiu8Z.mjs.map} +1 -1
  522. package/dist/{redirects-cV9g7V-0.mjs → redirects-CRIM55KH.mjs} +6 -6
  523. package/dist/{redirects-cV9g7V-0.mjs.map → redirects-CRIM55KH.mjs.map} +1 -1
  524. package/dist/{api-DdAsirBe.mjs → registry-BiO5WwPq.mjs} +483 -2462
  525. package/dist/registry-BiO5WwPq.mjs.map +1 -0
  526. package/dist/{registry-C581V4TP.mjs → registry-Cx-jEJOS.mjs} +9 -123
  527. package/dist/registry-Cx-jEJOS.mjs.map +1 -0
  528. package/dist/{relation-DtmAQH7s.mjs → relation-A6wLF_DS.mjs} +2 -2
  529. package/dist/{relation-DtmAQH7s.mjs.map → relation-A6wLF_DS.mjs.map} +1 -1
  530. package/dist/{relations-BBApI4e2.mjs → relations-BTPM2saV.mjs} +2 -2
  531. package/dist/{relations-BBApI4e2.mjs.map → relations-BTPM2saV.mjs.map} +1 -1
  532. package/dist/{relations-DItI3MN9.d.mts → relations-C7B46bcU.d.mts} +189 -30
  533. package/dist/relations-C7B46bcU.d.mts.map +1 -0
  534. package/dist/{relations-DICSyfsh.mjs → relations-Cj5hZ6Zg.mjs} +4 -4
  535. package/dist/{relations-DICSyfsh.mjs.map → relations-Cj5hZ6Zg.mjs.map} +1 -1
  536. package/dist/{request-cache-BSUptuJR.mjs → request-cache-DH9zdCOD.mjs} +1 -1
  537. package/dist/{request-cache-BSUptuJR.mjs.map → request-cache-DH9zdCOD.mjs.map} +1 -1
  538. package/dist/request-context.d.mts +1 -1
  539. package/dist/{request-meta-DgZJh3zG.mjs → request-meta-DRp5ENd_.mjs} +2 -2
  540. package/dist/{request-meta-DgZJh3zG.mjs.map → request-meta-DRp5ENd_.mjs.map} +1 -1
  541. package/dist/{resolve-DOhVEvty.mjs → resolve-DxUpTMmE.mjs} +1 -1
  542. package/dist/{resolve-DOhVEvty.mjs.map → resolve-DxUpTMmE.mjs.map} +1 -1
  543. package/dist/{runner-BiDvq5dv.mjs → runner-77GOZCnH.mjs} +443 -289
  544. package/dist/runner-77GOZCnH.mjs.map +1 -0
  545. package/dist/{runner-CgQbELYC.d.mts → runner-zgqA_CqP.d.mts} +16 -3
  546. package/dist/runner-zgqA_CqP.d.mts.map +1 -0
  547. package/dist/runtime.d.mts +11 -11
  548. package/dist/runtime.mjs +1 -1
  549. package/dist/{schema-BFyTg38T.mjs → schema-BY-oVntF.mjs} +3 -3
  550. package/dist/{schema-BFyTg38T.mjs.map → schema-BY-oVntF.mjs.map} +1 -1
  551. package/dist/{schema-CeQSNwB3.mjs → schema-Cw9kVK0I.mjs} +35 -5
  552. package/dist/{schema-CeQSNwB3.mjs.map → schema-Cw9kVK0I.mjs.map} +1 -1
  553. package/dist/{scopes-BVeuhGBE.mjs → scopes-vMR1uIbt.mjs} +3 -3
  554. package/dist/{scopes-BVeuhGBE.mjs.map → scopes-vMR1uIbt.mjs.map} +1 -1
  555. package/dist/{search-BpuPKZb0.mjs → search-CfT3KxO5.mjs} +4 -4
  556. package/dist/{search-BpuPKZb0.mjs.map → search-CfT3KxO5.mjs.map} +1 -1
  557. package/dist/{secrets-dtYs2hDo.mjs → secrets-BH2Qow1-.mjs} +3 -3
  558. package/dist/{secrets-dtYs2hDo.mjs.map → secrets-BH2Qow1-.mjs.map} +1 -1
  559. package/dist/{sections-COctk6hf.mjs → sections-DTfjyf1z.mjs} +2 -2
  560. package/dist/{sections-COctk6hf.mjs.map → sections-DTfjyf1z.mjs.map} +1 -1
  561. package/dist/seed/index.d.mts +2 -2
  562. package/dist/seed/index.mjs +24 -22
  563. package/dist/seo/index.d.mts +3 -3
  564. package/dist/seo/index.mjs +16 -14
  565. package/dist/seo/index.mjs.map +1 -1
  566. package/dist/{seo-AFpVvTqr.mjs → seo-BSiTIXzd.mjs} +1 -1
  567. package/dist/{seo-AFpVvTqr.mjs.map → seo-BSiTIXzd.mjs.map} +1 -1
  568. package/dist/{seo-CvDxfvFb.mjs → seo-Cm-dtvgk.mjs} +2 -2
  569. package/dist/{seo-CvDxfvFb.mjs.map → seo-Cm-dtvgk.mjs.map} +1 -1
  570. package/dist/{service-xMiMSTsR.mjs → service-DYsDHEKe.mjs} +3 -3
  571. package/dist/{service-xMiMSTsR.mjs.map → service-DYsDHEKe.mjs.map} +1 -1
  572. package/dist/{session-user-BzVemLkw.mjs → session-user-CNAfvsdw.mjs} +1 -1
  573. package/dist/{session-user-BzVemLkw.mjs.map → session-user-CNAfvsdw.mjs.map} +1 -1
  574. package/dist/{settings-B372MV0p.mjs → settings-BgN-0DT-.mjs} +6 -6
  575. package/dist/{settings-B372MV0p.mjs.map → settings-BgN-0DT-.mjs.map} +1 -1
  576. package/dist/{settings-BE9aqn_9.mjs → settings-DjtkUo__.mjs} +3 -3
  577. package/dist/{settings-BE9aqn_9.mjs.map → settings-DjtkUo__.mjs.map} +1 -1
  578. package/dist/{setup-complete-DF7TAfD5.mjs → setup-complete-CcpXaPU7.mjs} +2 -2
  579. package/dist/{setup-complete-DF7TAfD5.mjs.map → setup-complete-CcpXaPU7.mjs.map} +1 -1
  580. package/dist/{setup-nonce-Do-U_cg9.mjs → setup-nonce-Reyw30jg.mjs} +1 -1
  581. package/dist/{setup-nonce-Do-U_cg9.mjs.map → setup-nonce-Reyw30jg.mjs.map} +1 -1
  582. package/dist/{single-flight-cache-B0eLKS6K.mjs → single-flight-cache-BPuGPdcN.mjs} +2 -2
  583. package/dist/{single-flight-cache-B0eLKS6K.mjs.map → single-flight-cache-BPuGPdcN.mjs.map} +1 -1
  584. package/dist/{site-url-Bowsi9T-.mjs → site-url-Cs_V9n3V.mjs} +3 -3
  585. package/dist/{site-url-Bowsi9T-.mjs.map → site-url-Cs_V9n3V.mjs.map} +1 -1
  586. package/dist/{snapshot-B5h6aPMh.mjs → snapshot-DSB8P7Lm.mjs} +1 -1
  587. package/dist/{snapshot-B5h6aPMh.mjs.map → snapshot-DSB8P7Lm.mjs.map} +1 -1
  588. package/dist/{ssrf-Cpvg_n3A.mjs → ssrf-DqebVE34.mjs} +21 -5
  589. package/dist/{ssrf-Cpvg_n3A.mjs.map → ssrf-DqebVE34.mjs.map} +1 -1
  590. package/dist/{state-DYwPue40.mjs → state-BJfU9Uzw.mjs} +12 -4
  591. package/dist/state-BJfU9Uzw.mjs.map +1 -0
  592. package/dist/{status-CWVYpbD8.mjs → status-DDu8HIQl.mjs} +1 -1
  593. package/dist/{status-CWVYpbD8.mjs.map → status-DDu8HIQl.mjs.map} +1 -1
  594. package/dist/storage/local.d.mts +1 -1
  595. package/dist/storage/local.mjs +3 -3
  596. package/dist/storage/s3.d.mts +1 -1
  597. package/dist/storage/s3.mjs +1 -1
  598. package/dist/target-D0o26Azo.mjs +27 -0
  599. package/dist/target-D0o26Azo.mjs.map +1 -0
  600. package/dist/{taxonomies-Dk3MAklL.mjs → taxonomies-CPGxo-bn.mjs} +5 -5
  601. package/dist/{taxonomies-Dk3MAklL.mjs.map → taxonomies-CPGxo-bn.mjs.map} +1 -1
  602. package/dist/{taxonomies-B0o9ExNV.mjs → taxonomies-kmyudNGP.mjs} +7 -7
  603. package/dist/{taxonomies-B0o9ExNV.mjs.map → taxonomies-kmyudNGP.mjs.map} +1 -1
  604. package/dist/{taxonomy-CPeM8d2o.mjs → taxonomy-8X09dLN5.mjs} +4 -4
  605. package/dist/{taxonomy-CPeM8d2o.mjs.map → taxonomy-8X09dLN5.mjs.map} +1 -1
  606. package/dist/{tokens-C39klX8R.mjs → tokens-gTS-IVUp.mjs} +1 -1
  607. package/dist/{tokens-C39klX8R.mjs.map → tokens-gTS-IVUp.mjs.map} +1 -1
  608. package/dist/{transaction-NQj4VJ7Z.mjs → transaction-0KOpOyMu.mjs} +2 -1
  609. package/dist/{transaction-NQj4VJ7Z.mjs.map → transaction-0KOpOyMu.mjs.map} +1 -1
  610. package/dist/{transport-B_rfeU-a.d.mts → transport-_6R1ub7t.d.mts} +1 -1
  611. package/dist/{transport-B_rfeU-a.d.mts.map → transport-_6R1ub7t.d.mts.map} +1 -1
  612. package/dist/{transport--CfOflrp.mjs → transport-fX59xaJz.mjs} +4 -3
  613. package/dist/{transport--CfOflrp.mjs.map → transport-fX59xaJz.mjs.map} +1 -1
  614. package/dist/{trusted-proxy-CEVekpby.mjs → trusted-proxy-fe6nEgR4.mjs} +1 -1
  615. package/dist/{trusted-proxy-CEVekpby.mjs.map → trusted-proxy-fe6nEgR4.mjs.map} +1 -1
  616. package/dist/types-75KIqzL9.mjs.map +1 -1
  617. package/dist/{types-BW1syrlw.mjs → types-BXUmsINL.mjs} +1 -1
  618. package/dist/{types-BW1syrlw.mjs.map → types-BXUmsINL.mjs.map} +1 -1
  619. package/dist/{types-CPx2lIGo.d.mts → types-Blb-MRC3.d.mts} +1 -1
  620. package/dist/{types-CPx2lIGo.d.mts.map → types-Blb-MRC3.d.mts.map} +1 -1
  621. package/dist/{types-C3_AbkDP.d.mts → types-Br-ERaxh.d.mts} +2 -1
  622. package/dist/types-Br-ERaxh.d.mts.map +1 -0
  623. package/dist/{types-BPuloofb.d.mts → types-BzBd3PeX.d.mts} +1 -1
  624. package/dist/{types-BPuloofb.d.mts.map → types-BzBd3PeX.d.mts.map} +1 -1
  625. package/dist/{types-DefV3OiO.d.mts → types-CLqvnd05.d.mts} +1 -1
  626. package/dist/{types-DefV3OiO.d.mts.map → types-CLqvnd05.d.mts.map} +1 -1
  627. package/dist/{types-X0MThr7x.d.mts → types-D5VVLr7J.d.mts} +1 -1
  628. package/dist/{types-X0MThr7x.d.mts.map → types-D5VVLr7J.d.mts.map} +1 -1
  629. package/dist/{types-CAGdePf3.d.mts → types-DHxzBjbD.d.mts} +10 -1
  630. package/dist/types-DHxzBjbD.d.mts.map +1 -0
  631. package/dist/{types-D4_1Ga4L.d.mts → types-DJQxln7q.d.mts} +3 -3
  632. package/dist/{types-D4_1Ga4L.d.mts.map → types-DJQxln7q.d.mts.map} +1 -1
  633. package/dist/{types-CtzytVXn.mjs → types-DM3vioBY.mjs} +1 -1
  634. package/dist/{types-CtzytVXn.mjs.map → types-DM3vioBY.mjs.map} +1 -1
  635. package/dist/{types-CFUfyt1s.d.mts → types-D_DWe7k9.d.mts} +1 -1
  636. package/dist/{types-CFUfyt1s.d.mts.map → types-D_DWe7k9.d.mts.map} +1 -1
  637. package/dist/{types-Cs-TFLNf.d.mts → types-Vg6f5i3s.d.mts} +3 -3
  638. package/dist/{types-Cs-TFLNf.d.mts.map → types-Vg6f5i3s.d.mts.map} +1 -1
  639. package/dist/{upload-attempts-hlne3iA1.mjs → upload-attempts-CNyGSepg.mjs} +1 -1
  640. package/dist/{upload-attempts-hlne3iA1.mjs.map → upload-attempts-CNyGSepg.mjs.map} +1 -1
  641. package/dist/{url-pattern-BHS0V5Ng.mjs → url-pattern-K-KdXcSe.mjs} +1 -1
  642. package/dist/{url-pattern-BHS0V5Ng.mjs.map → url-pattern-K-KdXcSe.mjs.map} +1 -1
  643. package/dist/{user-sHgJ62SP.mjs → user-CtYY0tp2.mjs} +2 -2
  644. package/dist/{user-sHgJ62SP.mjs.map → user-CtYY0tp2.mjs.map} +1 -1
  645. package/dist/{utils-D4vD_Y98.mjs → utils-HztC73hq.mjs} +1 -1
  646. package/dist/{utils-D4vD_Y98.mjs.map → utils-HztC73hq.mjs.map} +1 -1
  647. package/dist/{validate-BkqIG_Uj.mjs → validate-Dd-IUjnZ.mjs} +2 -2
  648. package/dist/{validate-BkqIG_Uj.mjs.map → validate-Dd-IUjnZ.mjs.map} +1 -1
  649. package/dist/{validate-DeGtBN84.d.mts → validate-DlqdOZXB.d.mts} +5 -5
  650. package/dist/{validate-DeGtBN84.d.mts.map → validate-DlqdOZXB.d.mts.map} +1 -1
  651. package/dist/{validation-CiJb_X5o.mjs → validation-D83n7Npi.mjs} +10 -8
  652. package/dist/{validation-CiJb_X5o.mjs.map → validation-D83n7Npi.mjs.map} +1 -1
  653. package/dist/version-ri-sCyAp.mjs +7 -0
  654. package/dist/{version-x0NjTScG.mjs.map → version-ri-sCyAp.mjs.map} +1 -1
  655. package/dist/{widgets-Cwzvww0W.mjs → widgets-CpmZd9Q-.mjs} +4 -4
  656. package/dist/{widgets-Cwzvww0W.mjs.map → widgets-CpmZd9Q-.mjs.map} +1 -1
  657. package/dist/{wxr-taxonomies-CmC0ZsDr.d.mts → wxr-taxonomies-BFd0y72_.d.mts} +1 -1
  658. package/dist/{wxr-taxonomies-CmC0ZsDr.d.mts.map → wxr-taxonomies-BFd0y72_.d.mts.map} +1 -1
  659. package/dist/{wxr-taxonomies-QZjWxjyk.mjs → wxr-taxonomies-Df4vxWcU.mjs} +4 -4
  660. package/dist/{wxr-taxonomies-QZjWxjyk.mjs.map → wxr-taxonomies-Df4vxWcU.mjs.map} +1 -1
  661. package/dist/{zod-generator-D11MhxG4.mjs → zod-generator-DylzusHh.mjs} +24 -18
  662. package/dist/zod-generator-DylzusHh.mjs.map +1 -0
  663. package/package.json +32 -8
  664. package/src/api/errors.ts +20 -0
  665. package/src/api/handlers/index.ts +17 -0
  666. package/src/api/handlers/media-allowlist.ts +1 -0
  667. package/src/api/handlers/media-folders.ts +130 -0
  668. package/src/api/handlers/media-usage-activation.ts +102 -0
  669. package/src/api/handlers/media-usage.ts +108 -0
  670. package/src/api/handlers/media.ts +62 -2
  671. package/src/api/handlers/registry.ts +134 -316
  672. package/src/api/openapi/document.ts +192 -2
  673. package/src/api/schemas/media-usage.ts +77 -0
  674. package/src/api/schemas/media.ts +69 -0
  675. package/src/astro/integration/index.ts +46 -14
  676. package/src/astro/integration/routes.ts +18 -0
  677. package/src/astro/integration/runtime.ts +3 -0
  678. package/src/astro/integration/vite-config.ts +2 -7
  679. package/src/astro/middleware/auth.ts +4 -0
  680. package/src/astro/middleware/scoped-db.ts +21 -0
  681. package/src/astro/middleware.ts +67 -17
  682. package/src/astro/routes/api/admin/media-usage/activation.ts +42 -0
  683. package/src/astro/routes/api/admin/media-usage/progress.ts +37 -0
  684. package/src/astro/routes/api/admin/plugins/registry/artifact.ts +246 -96
  685. package/src/astro/routes/api/auth/dev-bypass.ts +0 -4
  686. package/src/astro/routes/api/auth/me.ts +14 -2
  687. package/src/astro/routes/api/media/[id].ts +3 -0
  688. package/src/astro/routes/api/media/folders/[id].ts +55 -0
  689. package/src/astro/routes/api/media/folders/index.ts +37 -0
  690. package/src/astro/routes/api/media.ts +12 -2
  691. package/src/astro/routes/api/setup/dev-bypass.ts +14 -7
  692. package/src/astro/routes/api/setup/index.ts +5 -13
  693. package/src/astro/types.ts +11 -1
  694. package/src/cli/commands/migrate.ts +593 -0
  695. package/src/cli/index.ts +2 -23
  696. package/src/client/index.ts +140 -2
  697. package/src/client/transport.ts +6 -2
  698. package/src/components/EmDashImage.astro +154 -50
  699. package/src/components/Gallery.astro +7 -0
  700. package/src/components/InlinePortableTextEditor.tsx +130 -1
  701. package/src/components/LiveSearch.astro +23 -0
  702. package/src/components/inline-code-block.tsx +180 -49
  703. package/src/content/converters/gallery.ts +2 -0
  704. package/src/content/converters/types.ts +2 -0
  705. package/src/database/connection.ts +2 -31
  706. package/src/database/index.ts +9 -3
  707. package/src/database/json-recordset.ts +16 -0
  708. package/src/database/migrations/019_i18n.ts +1 -1
  709. package/src/database/migrations/071_restore_content_bylines_table.ts +43 -0
  710. package/src/database/migrations/072_media_folders.ts +72 -0
  711. package/src/database/migrations/073_media_focal_point.ts +57 -0
  712. package/src/database/migrations/policy.ts +70 -0
  713. package/src/database/migrations/runner.ts +57 -5
  714. package/src/database/repositories/content.ts +5 -4
  715. package/src/database/repositories/media-folders.ts +115 -0
  716. package/src/database/repositories/media-usage-work.ts +395 -111
  717. package/src/database/repositories/media-usage.ts +1038 -10
  718. package/src/database/repositories/media.ts +97 -29
  719. package/src/database/transaction.ts +1 -0
  720. package/src/database/types.ts +13 -0
  721. package/src/db/adapters.ts +52 -4
  722. package/src/db/index.ts +3 -1
  723. package/src/db/libsql-migrations.ts +72 -0
  724. package/src/db/node-sqlite-compat.ts +78 -0
  725. package/src/db/postgres-migrations.ts +59 -0
  726. package/src/db/sqlite-migrations.ts +37 -0
  727. package/src/db/sqlite.ts +3 -3
  728. package/src/emdash-runtime.ts +154 -120
  729. package/src/fields/image.ts +7 -1
  730. package/src/fields/types.ts +6 -2
  731. package/src/i18n/normalize.ts +31 -0
  732. package/src/index.ts +12 -1
  733. package/src/media/focal-point.ts +37 -0
  734. package/src/media/local-runtime.ts +8 -0
  735. package/src/media/mime.ts +1 -0
  736. package/src/media/normalize.ts +10 -0
  737. package/src/media/types.ts +10 -0
  738. package/src/media/usage/activation.ts +162 -35
  739. package/src/media/usage/capture-triggers.ts +19 -3
  740. package/src/media/usage/collection-deletion-processor.ts +7 -3
  741. package/src/media/usage/collection-deletion.ts +10 -0
  742. package/src/media/usage/content-refresh.ts +258 -39
  743. package/src/media/usage/content-snapshots.ts +128 -31
  744. package/src/media/usage/extractor.ts +32 -13
  745. package/src/media/usage/maintenance-engine.ts +166 -0
  746. package/src/media/usage/reconciliation-processor.ts +106 -29
  747. package/src/media/usage/reconciliation.ts +90 -7
  748. package/src/media/usage/work-processor.ts +342 -101
  749. package/src/migrations/config-loader.ts +205 -0
  750. package/src/migrations/direct-executor.ts +141 -0
  751. package/src/migrations/identity.ts +46 -0
  752. package/src/migrations/index.ts +19 -0
  753. package/src/migrations/integration-metadata.ts +67 -0
  754. package/src/migrations/manifest-builder.ts +55 -0
  755. package/src/migrations/manifest-writer.ts +63 -0
  756. package/src/migrations/manifest.ts +197 -0
  757. package/src/migrations/protocol.ts +59 -0
  758. package/src/migrations/target.ts +35 -0
  759. package/src/plugins/index.ts +1 -0
  760. package/src/plugins/save-rejection.ts +20 -0
  761. package/src/plugins/scheduler/node.ts +1 -13
  762. package/src/plugins/scheduler/types.ts +0 -2
  763. package/src/plugins/state.ts +13 -3
  764. package/src/registry/artifact-fetch.ts +530 -0
  765. package/src/registry/config.ts +24 -6
  766. package/src/registry/types.ts +12 -15
  767. package/src/schema/types.ts +1 -0
  768. package/src/schema/zod-generator.ts +12 -4
  769. package/src/security/ssrf.ts +21 -2
  770. package/src/seed/apply.ts +4 -4
  771. package/src/ui.ts +2 -0
  772. package/src/virtual-modules.d.ts +8 -0
  773. package/dist/adapters-34EN2eXO.d.mts.map +0 -1
  774. package/dist/api-DdAsirBe.mjs.map +0 -1
  775. package/dist/collection-deletion-C8BugSGz.mjs.map +0 -1
  776. package/dist/content-BNfwh7xX.mjs.map +0 -1
  777. package/dist/content-refresh-DNERP4-f.mjs.map +0 -1
  778. package/dist/errors-CLUPm7aD.mjs.map +0 -1
  779. package/dist/index-C3Y-rsyN.d.mts.map +0 -1
  780. package/dist/media-BWWWb8kg.mjs.map +0 -1
  781. package/dist/media-CzhAn2--.mjs.map +0 -1
  782. package/dist/media-allowlist-BD6HhgXA.mjs.map +0 -1
  783. package/dist/media-usage-De3qvwjc.mjs.map +0 -1
  784. package/dist/media-usage-work-DrCbqBcl.mjs.map +0 -1
  785. package/dist/menus-CC-yn1F9.mjs.map +0 -1
  786. package/dist/mime-DfIVjNkr.mjs.map +0 -1
  787. package/dist/normalize-Bc-s_r-a.mjs.map +0 -1
  788. package/dist/placeholder-CzW7Lshs.d.mts.map +0 -1
  789. package/dist/registry-C581V4TP.mjs.map +0 -1
  790. package/dist/relations-DItI3MN9.d.mts.map +0 -1
  791. package/dist/runner-BiDvq5dv.mjs.map +0 -1
  792. package/dist/runner-CgQbELYC.d.mts.map +0 -1
  793. package/dist/state-DYwPue40.mjs.map +0 -1
  794. package/dist/types-C3_AbkDP.d.mts.map +0 -1
  795. package/dist/types-CAGdePf3.d.mts.map +0 -1
  796. package/dist/version-x0NjTScG.mjs +0 -7
  797. package/dist/zod-generator-D11MhxG4.mjs.map +0 -1
  798. /package/dist/{api-tokens-CyTsLszZ.mjs → api-tokens-B5NHLNUN.mjs} +0 -0
  799. /package/dist/{ssrf-DpXgKsna.mjs → ssrf-BPKzkm6O.mjs} +0 -0
  800. /package/dist/{types-CP1CflWf.mjs → types-D6OcIp2P.mjs} +0 -0
@@ -0,0 +1,22 @@
1
+ import { a as MigrationExecutorModule, c as MigrationTarget, i as MigrationExecutorFactoryContext, l as MigrationTargetOverrides, n as MigrationExecutor, o as MigrationReport, r as MigrationExecutorFactory, s as MigrationRequest, t as MigrationAction } from "../protocol-CuBEsqgE.mjs";
2
+ import { Dialect } from "kysely";
3
+
4
+ //#region src/migrations/direct-executor.d.ts
5
+ interface DirectMigrationExecutorOptions {
6
+ target: MigrationTarget;
7
+ createDialect: () => Dialect | Promise<Dialect>;
8
+ }
9
+ declare function createDirectMigrationExecutor(options: DirectMigrationExecutorOptions): MigrationExecutor;
10
+ //#endregion
11
+ //#region src/migrations/identity.d.ts
12
+ interface CoreMigrationIdentity {
13
+ emdashVersion: string;
14
+ names: readonly string[];
15
+ fingerprint: string;
16
+ }
17
+ declare function fingerprintMigrationSet(emdashVersion: string, names: readonly string[]): Promise<string>;
18
+ declare function createCoreMigrationIdentity(emdashVersion: string, names: readonly string[]): Promise<CoreMigrationIdentity>;
19
+ declare function getCoreMigrationIdentity(): Promise<CoreMigrationIdentity>;
20
+ //#endregion
21
+ export { type CoreMigrationIdentity, type DirectMigrationExecutorOptions, type MigrationAction, type MigrationExecutor, type MigrationExecutorFactory, type MigrationExecutorFactoryContext, type MigrationExecutorModule, type MigrationReport, type MigrationRequest, type MigrationTarget, type MigrationTargetOverrides, createCoreMigrationIdentity, createDirectMigrationExecutor, fingerprintMigrationSet, getCoreMigrationIdentity };
22
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/migrations/direct-executor.ts","../../src/migrations/identity.ts"],"mappings":";;;;UAiBiB,8BAAA;EAChB,MAAA,EAAQ,eAAA;EACR,aAAA,QAAqB,OAAA,GAAU,OAAA,CAAQ,OAAA;AAAA;AAAA,iBAqDxB,6BAAA,CACf,OAAA,EAAS,8BAAA,GACP,iBAAA;;;UCvEc,qBAAA;EAChB,aAAA;EACA,KAAA;EACA,WAAA;AAAA;AAAA,iBAcqB,uBAAA,CACrB,aAAA,UACA,KAAA,sBACE,OAAA;AAAA,iBAQmB,2BAAA,CACrB,aAAA,UACA,KAAA,sBACE,OAAA,CAAQ,qBAAA;AAAA,iBASK,wBAAA,CAAA,GAA4B,OAAA,CAAQ,qBAAA"}
@@ -0,0 +1,7 @@
1
+ import "../runner-77GOZCnH.mjs";
2
+ import "../dialect-helpers-BSbfaO3F.mjs";
3
+ import "../config-C3EBstHi.mjs";
4
+ import { n as fingerprintMigrationSet, r as getCoreMigrationIdentity, t as createCoreMigrationIdentity } from "../identity-DXmb4aVp.mjs";
5
+ import { t as createDirectMigrationExecutor } from "../direct-executor-BWeWiRYB.mjs";
6
+
7
+ export { createCoreMigrationIdentity, createDirectMigrationExecutor, fingerprintMigrationSet, getCoreMigrationIdentity };
@@ -33,4 +33,4 @@ function parseAllowedMimeTypes(rawValidation) {
33
33
 
34
34
  //#endregion
35
35
  export { normalizeMime as n, parseAllowedMimeTypes as r, matchesMimeAllowlist as t };
36
- //# sourceMappingURL=mime-DfIVjNkr.mjs.map
36
+ //# sourceMappingURL=mime-CUIG6x84.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime-CUIG6x84.mjs","names":[],"sources":["../src/media/mime.ts"],"sourcesContent":["export function normalizeMime(mime: string): string {\n\treturn mime.split(\";\")[0].trim().toLowerCase();\n}\n\nexport function matchesMimeAllowlist(mime: string, allowList: readonly string[]): boolean {\n\tconst normalized = normalizeMime(mime);\n\tfor (const entry of allowList) {\n\t\tif (!entry || !entry.includes(\"/\")) continue;\n\t\tconst normalizedEntry = normalizeMime(entry);\n\t\tif (normalizedEntry.endsWith(\"/\")) {\n\t\t\tif (normalized.startsWith(normalizedEntry)) return true;\n\t\t} else if (normalized === normalizedEntry) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport const EXTENSION_TO_MIME: Readonly<Record<string, string>> = {\n\t\".pdf\": \"application/pdf\",\n\t\".png\": \"image/png\",\n\t\".jpg\": \"image/jpeg\",\n\t\".jpeg\": \"image/jpeg\",\n\t\".gif\": \"image/gif\",\n\t\".webp\": \"image/webp\",\n\t\".avif\": \"image/avif\",\n\t\".svg\": \"image/svg+xml\",\n\t\".mp3\": \"audio/mpeg\",\n\t\".wav\": \"audio/wav\",\n\t\".mp4\": \"video/mp4\",\n\t\".webm\": \"video/webm\",\n\t\".zip\": \"application/zip\",\n\t\".tar\": \"application/x-tar\",\n\t\".gz\": \"application/gzip\",\n\t\".csv\": \"text/csv\",\n\t\".doc\": \"application/msword\",\n\t\".docx\": \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n\t\".xls\": \"application/vnd.ms-excel\",\n\t\".xlsx\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\t\".txt\": \"text/plain\",\n\t\".rtf\": \"application/rtf\",\n\t\".vtt\": \"text/vtt\",\n\t\".srt\": \"application/x-subrip\",\n\t\".woff\": \"font/woff\",\n\t\".woff2\": \"font/woff2\",\n};\n\nconst VALID_MIME_RE = /^[a-z0-9][a-z0-9!#$&^_+\\-.]*\\/[a-z0-9!#$&^_+\\-.]*$/i;\n\nexport function expandExtensionShorthand(entry: string): string | null {\n\tconst trimmed = entry.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.includes(\"/\")) return VALID_MIME_RE.test(trimmed) ? trimmed : null;\n\tif (trimmed.startsWith(\".\")) {\n\t\treturn EXTENSION_TO_MIME[trimmed.toLowerCase()] ?? null;\n\t}\n\treturn null;\n}\n\n/**\n * Extract the `allowedMimeTypes` list from a `_emdash_fields.validation` row\n * (raw JSON string). Returns null when the value is missing, malformed, or the\n * list is empty — callers treat that as \"no field-specific constraint\".\n */\nexport function parseAllowedMimeTypes(rawValidation: string | null | undefined): string[] | null {\n\tif (!rawValidation) return null;\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(rawValidation);\n\t\tif (typeof parsed !== \"object\" || parsed === null) return null;\n\t\tconst list = (parsed as { allowedMimeTypes?: unknown }).allowedMimeTypes;\n\t\tif (!Array.isArray(list) || list.length === 0) return null;\n\t\treturn list.filter((entry): entry is string => typeof entry === \"string\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],"mappings":";AAAA,SAAgB,cAAc,MAAsB;AACnD,QAAO,KAAK,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,aAAa;;AAG/C,SAAgB,qBAAqB,MAAc,WAAuC;CACzF,MAAM,aAAa,cAAc,KAAK;AACtC,MAAK,MAAM,SAAS,WAAW;AAC9B,MAAI,CAAC,SAAS,CAAC,MAAM,SAAS,IAAI,CAAE;EACpC,MAAM,kBAAkB,cAAc,MAAM;AAC5C,MAAI,gBAAgB,SAAS,IAAI,EAChC;OAAI,WAAW,WAAW,gBAAgB,CAAE,QAAO;aACzC,eAAe,gBACzB,QAAO;;AAGT,QAAO;;;;;;;AAiDR,SAAgB,sBAAsB,eAA2D;AAChG,KAAI,CAAC,cAAe,QAAO;AAC3B,KAAI;EACH,MAAM,SAAkB,KAAK,MAAM,cAAc;AACjD,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;EAC1D,MAAM,OAAQ,OAA0C;AACxD,MAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,WAAW,EAAG,QAAO;AACtD,SAAO,KAAK,QAAQ,UAA2B,OAAO,UAAU,SAAS;SAClE;AACP,SAAO"}
@@ -20,4 +20,4 @@ function getAuthMode(config) {
20
20
 
21
21
  //#endregion
22
22
  export { getAuthMode as t };
23
- //# sourceMappingURL=mode-Bpe-tNNy.mjs.map
23
+ //# sourceMappingURL=mode-DaUM_MB2.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mode-Bpe-tNNy.mjs","names":[],"sources":["../src/auth/mode.ts"],"sourcesContent":["/**\n * Auth Mode Detection\n *\n * Determines which authentication provider is active based on config.\n * Supports both passkey (default) and external auth providers via AuthDescriptor.\n */\n\nimport type { EmDashConfig } from \"../astro/integration/runtime.js\";\nimport type {\n\tAuthDescriptor,\n\tAuthProviderDescriptor,\n\tAuthRouteDescriptor,\n\tAuthResult,\n\tExternalAuthConfig,\n} from \"./types.js\";\n\nexport type {\n\tAuthDescriptor,\n\tAuthProviderDescriptor,\n\tAuthRouteDescriptor,\n\tAuthResult,\n\tExternalAuthConfig,\n};\n\n/**\n * Passkey auth mode (default)\n */\nexport interface PasskeyAuthMode {\n\ttype: \"passkey\";\n}\n\n/**\n * External auth provider mode (Cloudflare Access, etc.)\n */\nexport interface ExternalAuthMode {\n\ttype: \"external\";\n\t/** Provider type identifier (e.g., \"cloudflare-access\") */\n\tproviderType: string;\n\t/** Module to import for authentication */\n\tentrypoint: string;\n\t/** Provider-specific configuration */\n\tconfig: unknown;\n}\n\n/**\n * Union of all auth modes\n */\nexport type AuthMode = PasskeyAuthMode | ExternalAuthMode;\n\n/**\n * Extended config type with auth.\n *\n * This is the same as `EmDashConfig` with an optional `auth` field.\n * Kept for backwards compatibility — prefer `EmDashConfig` in new code\n * since `getAuthMode` now accepts `EmDashConfig` directly.\n */\nexport interface EmDashConfigWithAuth extends EmDashConfig {\n\tauth?: AuthDescriptor;\n}\n\n/**\n * Determine the active auth mode from config.\n *\n * Accepts `EmDashConfig` (or subtype) — checks for `auth` field via duck typing.\n *\n * @param config EmDash configuration\n * @returns The active auth mode\n */\nexport function getAuthMode(\n\tconfig: (EmDashConfig & { auth?: AuthDescriptor }) | null | undefined,\n): AuthMode {\n\tconst auth = config?.auth;\n\n\t// Check for AuthDescriptor (transparent external auth like Cloudflare Access)\n\tif (auth && \"entrypoint\" in auth && auth.entrypoint) {\n\t\treturn {\n\t\t\ttype: \"external\",\n\t\t\tproviderType: auth.type,\n\t\t\tentrypoint: auth.entrypoint,\n\t\t\tconfig: auth.config,\n\t\t};\n\t}\n\n\t// Default to passkey\n\treturn { type: \"passkey\" };\n}\n\n/**\n * Check if an external auth provider is active\n */\nexport function isExternalAuthEnabled(\n\tconfig: (EmDashConfig & { auth?: AuthDescriptor }) | null | undefined,\n): boolean {\n\treturn getAuthMode(config).type === \"external\";\n}\n\n/**\n * Get external auth config if enabled\n */\nexport function getExternalAuthConfig(\n\tconfig: (EmDashConfig & { auth?: AuthDescriptor }) | null | undefined,\n): ExternalAuthMode | null {\n\tconst mode = getAuthMode(config);\n\tif (mode.type === \"external\") {\n\t\treturn mode;\n\t}\n\treturn null;\n}\n"],"mappings":";;;;;;;;;AAoEA,SAAgB,YACf,QACW;CACX,MAAM,OAAO,QAAQ;AAGrB,KAAI,QAAQ,gBAAgB,QAAQ,KAAK,WACxC,QAAO;EACN,MAAM;EACN,cAAc,KAAK;EACnB,YAAY,KAAK;EACjB,QAAQ,KAAK;EACb;AAIF,QAAO,EAAE,MAAM,WAAW"}
1
+ {"version":3,"file":"mode-DaUM_MB2.mjs","names":[],"sources":["../src/auth/mode.ts"],"sourcesContent":["/**\n * Auth Mode Detection\n *\n * Determines which authentication provider is active based on config.\n * Supports both passkey (default) and external auth providers via AuthDescriptor.\n */\n\nimport type { EmDashConfig } from \"../astro/integration/runtime.js\";\nimport type {\n\tAuthDescriptor,\n\tAuthProviderDescriptor,\n\tAuthRouteDescriptor,\n\tAuthResult,\n\tExternalAuthConfig,\n} from \"./types.js\";\n\nexport type {\n\tAuthDescriptor,\n\tAuthProviderDescriptor,\n\tAuthRouteDescriptor,\n\tAuthResult,\n\tExternalAuthConfig,\n};\n\n/**\n * Passkey auth mode (default)\n */\nexport interface PasskeyAuthMode {\n\ttype: \"passkey\";\n}\n\n/**\n * External auth provider mode (Cloudflare Access, etc.)\n */\nexport interface ExternalAuthMode {\n\ttype: \"external\";\n\t/** Provider type identifier (e.g., \"cloudflare-access\") */\n\tproviderType: string;\n\t/** Module to import for authentication */\n\tentrypoint: string;\n\t/** Provider-specific configuration */\n\tconfig: unknown;\n}\n\n/**\n * Union of all auth modes\n */\nexport type AuthMode = PasskeyAuthMode | ExternalAuthMode;\n\n/**\n * Extended config type with auth.\n *\n * This is the same as `EmDashConfig` with an optional `auth` field.\n * Kept for backwards compatibility — prefer `EmDashConfig` in new code\n * since `getAuthMode` now accepts `EmDashConfig` directly.\n */\nexport interface EmDashConfigWithAuth extends EmDashConfig {\n\tauth?: AuthDescriptor;\n}\n\n/**\n * Determine the active auth mode from config.\n *\n * Accepts `EmDashConfig` (or subtype) — checks for `auth` field via duck typing.\n *\n * @param config EmDash configuration\n * @returns The active auth mode\n */\nexport function getAuthMode(\n\tconfig: (EmDashConfig & { auth?: AuthDescriptor }) | null | undefined,\n): AuthMode {\n\tconst auth = config?.auth;\n\n\t// Check for AuthDescriptor (transparent external auth like Cloudflare Access)\n\tif (auth && \"entrypoint\" in auth && auth.entrypoint) {\n\t\treturn {\n\t\t\ttype: \"external\",\n\t\t\tproviderType: auth.type,\n\t\t\tentrypoint: auth.entrypoint,\n\t\t\tconfig: auth.config,\n\t\t};\n\t}\n\n\t// Default to passkey\n\treturn { type: \"passkey\" };\n}\n\n/**\n * Check if an external auth provider is active\n */\nexport function isExternalAuthEnabled(\n\tconfig: (EmDashConfig & { auth?: AuthDescriptor }) | null | undefined,\n): boolean {\n\treturn getAuthMode(config).type === \"external\";\n}\n\n/**\n * Get external auth config if enabled\n */\nexport function getExternalAuthConfig(\n\tconfig: (EmDashConfig & { auth?: AuthDescriptor }) | null | undefined,\n): ExternalAuthMode | null {\n\tconst mode = getAuthMode(config);\n\tif (mode.type === \"external\") {\n\t\treturn mode;\n\t}\n\treturn null;\n}\n"],"mappings":";;;;;;;;;AAoEA,SAAgB,YACf,QACW;CACX,MAAM,OAAO,QAAQ;AAGrB,KAAI,QAAQ,gBAAgB,QAAQ,KAAK,WACxC,QAAO;EACN,MAAM;EACN,cAAc,KAAK;EACnB,YAAY,KAAK;EACjB,QAAQ,KAAK;EACb;AAIF,QAAO,EAAE,MAAM,WAAW"}
@@ -0,0 +1,46 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+
3
+ //#region src/db/node-sqlite-compat.ts
4
+ function openNodeSqliteDatabase(path, options = {}) {
5
+ const database = new DatabaseSync(path);
6
+ try {
7
+ database.exec("PRAGMA busy_timeout = 5000");
8
+ database.exec("PRAGMA foreign_keys = ON");
9
+ database.exec("PRAGMA cache_size = -16000");
10
+ if (options.journalMode === "wal") database.exec("PRAGMA journal_mode = WAL");
11
+ if (database.prepare("PRAGMA journal_mode").get()?.["journal_mode"] === "wal") database.exec("PRAGMA synchronous = NORMAL");
12
+ } catch (error) {
13
+ try {
14
+ database.close();
15
+ } catch {
16
+ throw new Error("SQLite setup failed and the connection could not be closed", { cause: error });
17
+ }
18
+ throw error;
19
+ }
20
+ return {
21
+ close: () => database.close(),
22
+ prepare(sql) {
23
+ const statement = database.prepare(sql);
24
+ return {
25
+ reader: statement.columns().length > 0,
26
+ all: (parameters) => statement.all(...toBindings(parameters)),
27
+ run: (parameters) => statement.run(...toBindings(parameters)),
28
+ iterate: (parameters) => statement.iterate(...toBindings(parameters))
29
+ };
30
+ }
31
+ };
32
+ }
33
+ function toBindings(parameters) {
34
+ return parameters.map((value) => {
35
+ if (value === null || typeof value === "string" || typeof value === "number") return value;
36
+ if (typeof value === "bigint") return value;
37
+ if (typeof value === "boolean") return value ? 1 : 0;
38
+ if (value === void 0) return null;
39
+ if (value instanceof Uint8Array) return value;
40
+ throw new TypeError(`Cannot bind ${Object.prototype.toString.call(value)} to SQLite`);
41
+ });
42
+ }
43
+
44
+ //#endregion
45
+ export { openNodeSqliteDatabase as t };
46
+ //# sourceMappingURL=node-sqlite-compat-Udb4Y-rs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-sqlite-compat-Udb4Y-rs.mjs","names":[],"sources":["../src/db/node-sqlite-compat.ts"],"sourcesContent":["import { DatabaseSync } from \"node:sqlite\";\n\nexport interface NodeSqliteCompatDatabase {\n\tclose(): void;\n\tprepare(sql: string): NodeSqliteCompatStatement;\n}\n\nexport interface NodeSqliteCompatStatement {\n\treadonly reader: boolean;\n\tall(parameters: ReadonlyArray<unknown>): unknown[];\n\trun(parameters: ReadonlyArray<unknown>): {\n\t\tchanges: number | bigint;\n\t\tlastInsertRowid: number | bigint;\n\t};\n\titerate(parameters: ReadonlyArray<unknown>): IterableIterator<unknown>;\n}\n\nexport interface NodeSqliteOpenOptions {\n\tjournalMode?: \"wal\";\n}\n\ntype SqliteBinding = null | number | bigint | string | Uint8Array;\n\nexport function openNodeSqliteDatabase(\n\tpath: string,\n\toptions: NodeSqliteOpenOptions = {},\n): NodeSqliteCompatDatabase {\n\tconst database = new DatabaseSync(path);\n\n\ttry {\n\t\tdatabase.exec(\"PRAGMA busy_timeout = 5000\");\n\t\tdatabase.exec(\"PRAGMA foreign_keys = ON\");\n\t\t// Negative cache_size values are kibibytes.\n\t\tdatabase.exec(\"PRAGMA cache_size = -16000\");\n\t\tif (options.journalMode === \"wal\") {\n\t\t\tdatabase.exec(\"PRAGMA journal_mode = WAL\");\n\t\t}\n\t\tconst journalMode = database.prepare(\"PRAGMA journal_mode\").get()?.[\"journal_mode\"];\n\t\tif (journalMode === \"wal\") {\n\t\t\tdatabase.exec(\"PRAGMA synchronous = NORMAL\");\n\t\t}\n\t} catch (error) {\n\t\ttry {\n\t\t\tdatabase.close();\n\t\t} catch {\n\t\t\tthrow new Error(\"SQLite setup failed and the connection could not be closed\", {\n\t\t\t\tcause: error,\n\t\t\t});\n\t\t}\n\t\tthrow error;\n\t}\n\n\treturn {\n\t\tclose: () => database.close(),\n\t\tprepare(sql) {\n\t\t\tconst statement = database.prepare(sql);\n\t\t\treturn {\n\t\t\t\treader: statement.columns().length > 0,\n\t\t\t\tall: (parameters) => statement.all(...toBindings(parameters)),\n\t\t\t\trun: (parameters) => statement.run(...toBindings(parameters)),\n\t\t\t\titerate: (parameters) => statement.iterate(...toBindings(parameters)),\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction toBindings(parameters: ReadonlyArray<unknown>): SqliteBinding[] {\n\treturn parameters.map((value) => {\n\t\tif (value === null || typeof value === \"string\" || typeof value === \"number\") {\n\t\t\treturn value;\n\t\t}\n\t\tif (typeof value === \"bigint\") return value;\n\t\tif (typeof value === \"boolean\") return value ? 1 : 0;\n\t\tif (value === undefined) return null;\n\t\tif (value instanceof Uint8Array) return value;\n\t\tthrow new TypeError(`Cannot bind ${Object.prototype.toString.call(value)} to SQLite`);\n\t});\n}\n"],"mappings":";;;AAuBA,SAAgB,uBACf,MACA,UAAiC,EAAE,EACR;CAC3B,MAAM,WAAW,IAAI,aAAa,KAAK;AAEvC,KAAI;AACH,WAAS,KAAK,6BAA6B;AAC3C,WAAS,KAAK,2BAA2B;AAEzC,WAAS,KAAK,6BAA6B;AAC3C,MAAI,QAAQ,gBAAgB,MAC3B,UAAS,KAAK,4BAA4B;AAG3C,MADoB,SAAS,QAAQ,sBAAsB,CAAC,KAAK,GAAG,oBAChD,MACnB,UAAS,KAAK,8BAA8B;UAErC,OAAO;AACf,MAAI;AACH,YAAS,OAAO;UACT;AACP,SAAM,IAAI,MAAM,8DAA8D,EAC7E,OAAO,OACP,CAAC;;AAEH,QAAM;;AAGP,QAAO;EACN,aAAa,SAAS,OAAO;EAC7B,QAAQ,KAAK;GACZ,MAAM,YAAY,SAAS,QAAQ,IAAI;AACvC,UAAO;IACN,QAAQ,UAAU,SAAS,CAAC,SAAS;IACrC,MAAM,eAAe,UAAU,IAAI,GAAG,WAAW,WAAW,CAAC;IAC7D,MAAM,eAAe,UAAU,IAAI,GAAG,WAAW,WAAW,CAAC;IAC7D,UAAU,eAAe,UAAU,QAAQ,GAAG,WAAW,WAAW,CAAC;IACrE;;EAEF;;AAGF,SAAS,WAAW,YAAqD;AACxE,QAAO,WAAW,KAAK,UAAU;AAChC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,SACnE,QAAO;AAER,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,UAAW,QAAO,QAAQ,IAAI;AACnD,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,iBAAiB,WAAY,QAAO;AACxC,QAAM,IAAI,UAAU,eAAe,OAAO,UAAU,SAAS,KAAK,MAAM,CAAC,YAAY;GACpF"}
@@ -1,4 +1,16 @@
1
+ import { n as normalizeFocalPoint } from "./focal-point-BeWFo6Xx.mjs";
2
+
1
3
  //#region src/media/normalize.ts
4
+ /**
5
+ * Media Value Normalization
6
+ *
7
+ * Normalizes media field values into a consistent shape regardless of
8
+ * creation path (seed scripts, media picker, WP import, URL input).
9
+ *
10
+ * Called at content create/update time when a media provider is available,
11
+ * filling in missing dimensions, storageKey, mimeType, and filename from
12
+ * the provider's `get()` method.
13
+ */
2
14
  const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
3
15
  const URL_PATTERN = /^https?:\/\//;
4
16
  /**
@@ -77,6 +89,7 @@ async function resolveInternalUrl(url, getProvider) {
77
89
  id: "",
78
90
  src: url
79
91
  };
92
+ const focalPoint = normalizeFocalPoint(item.focalX, item.focalY);
80
93
  return {
81
94
  provider: "local",
82
95
  id: item.id,
@@ -84,6 +97,7 @@ async function resolveInternalUrl(url, getProvider) {
84
97
  mimeType: item.mimeType,
85
98
  width: item.width,
86
99
  height: item.height,
100
+ ...focalPoint,
87
101
  blurhash: item.blurhash,
88
102
  dominantColor: item.dominantColor,
89
103
  alt: item.alt,
@@ -100,6 +114,7 @@ async function resolveLocalId(id, getProvider) {
100
114
  return null;
101
115
  }
102
116
  if (!item) return null;
117
+ const focalPoint = normalizeFocalPoint(item.focalX, item.focalY);
103
118
  return {
104
119
  provider: "local",
105
120
  id: item.id,
@@ -107,6 +122,7 @@ async function resolveLocalId(id, getProvider) {
107
122
  mimeType: item.mimeType,
108
123
  width: item.width,
109
124
  height: item.height,
125
+ ...focalPoint,
110
126
  blurhash: item.blurhash,
111
127
  dominantColor: item.dominantColor,
112
128
  alt: item.alt,
@@ -148,6 +164,11 @@ function recordToMediaValue(obj) {
148
164
  if (typeof obj.mimeType === "string") result.mimeType = obj.mimeType;
149
165
  if (typeof obj.width === "number") result.width = obj.width;
150
166
  if (typeof obj.height === "number") result.height = obj.height;
167
+ const focalPoint = normalizeFocalPoint(obj.focalX, obj.focalY);
168
+ if (focalPoint) {
169
+ result.focalX = focalPoint.focalX;
170
+ result.focalY = focalPoint.focalY;
171
+ }
151
172
  if (typeof obj.blurhash === "string") result.blurhash = obj.blurhash;
152
173
  if (typeof obj.dominantColor === "string") result.dominantColor = obj.dominantColor;
153
174
  if (typeof obj.alt === "string") result.alt = obj.alt;
@@ -157,4 +178,4 @@ function recordToMediaValue(obj) {
157
178
 
158
179
  //#endregion
159
180
  export { normalizeMediaValue as n, INTERNAL_MEDIA_PREFIX as t };
160
- //# sourceMappingURL=normalize-Bc-s_r-a.mjs.map
181
+ //# sourceMappingURL=normalize-CSxdKBVa.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-CSxdKBVa.mjs","names":[],"sources":["../src/media/normalize.ts"],"sourcesContent":["/**\n * Media Value Normalization\n *\n * Normalizes media field values into a consistent shape regardless of\n * creation path (seed scripts, media picker, WP import, URL input).\n *\n * Called at content create/update time when a media provider is available,\n * filling in missing dimensions, storageKey, mimeType, and filename from\n * the provider's `get()` method.\n */\n\nimport { normalizeFocalPoint } from \"./focal-point.js\";\nimport type { MediaProvider, MediaProviderItem, MediaValue } from \"./types.js\";\n\nexport const INTERNAL_MEDIA_PREFIX = \"/_emdash/api/media/file/\";\nconst URL_PATTERN = /^https?:\\/\\//;\n\n/**\n * Normalize a media field value into a consistent MediaValue shape.\n *\n * - `null`/`undefined` → `null`\n * - Bare URL string → `{ provider: \"external\", id: \"\", src: url }`\n * - Bare internal media URL → resolved via local provider's `get()`\n * - Bare local media ID → resolved via local provider's `get()`\n * - Object with `provider` + `id` → enriched with missing fields from provider\n */\nexport async function normalizeMediaValue(\n\tvalue: unknown,\n\tgetProvider: (id: string) => MediaProvider | undefined,\n): Promise<MediaValue | null> {\n\tif (value == null) return null;\n\n\t// Bare string URL\n\tif (typeof value === \"string\") {\n\t\treturn normalizeStringUrl(value, getProvider);\n\t}\n\n\t// Not an object — can't normalize\n\tif (!isRecord(value)) return null;\n\n\t// Must have at least an id to be a valid media value\n\tif (!(\"id\" in value) && !(\"src\" in value)) return null;\n\n\tconst provider = (typeof value.provider === \"string\" ? value.provider : undefined) || \"local\";\n\tconst id = typeof value.id === \"string\" ? value.id : \"\";\n\n\t// External URLs — return as-is, no server-side dimension detection\n\tif (provider === \"external\") {\n\t\treturn recordToMediaValue(value);\n\t}\n\n\t// Build the base value from the input\n\tconst result: MediaValue = { ...recordToMediaValue(value), provider };\n\n\t// For local media, strip `src` — it's derived at display time from storageKey\n\tif (provider === \"local\") {\n\t\tdelete result.src;\n\t}\n\n\t// Determine if we need to call the provider\n\tconst needsDimensions = result.width == null || result.height == null;\n\tconst needsStorageKey = provider === \"local\" && !result.meta?.storageKey;\n\tconst needsFileInfo = !result.mimeType || !result.filename;\n\t// LQIP placeholders are immutable facts of the bytes: if an image record is\n\t// missing them, the provider may have gained them since (e.g. content saved\n\t// before LQIP backfill ran, or the row's blurhash was populated later). Pull\n\t// them on every image lookup so the LQIP backfill in mergeProviderData runs.\n\tconst needsLqip =\n\t\t(result.mimeType ?? \"\").startsWith(\"image/\") &&\n\t\t(result.blurhash == null || result.dominantColor == null);\n\tconst needsLookup = needsDimensions || needsStorageKey || needsFileInfo || needsLqip;\n\n\tif (!needsLookup || !id) return result;\n\n\t// Try to enrich from provider\n\tconst mediaProvider = getProvider(provider);\n\tif (!mediaProvider?.get) return result;\n\n\tlet providerItem: MediaProviderItem | null;\n\ttry {\n\t\tproviderItem = await mediaProvider.get(id);\n\t} catch {\n\t\treturn result;\n\t}\n\n\tif (!providerItem) return result;\n\n\treturn mergeProviderData(result, providerItem);\n}\n\nasync function normalizeStringUrl(\n\turl: string,\n\tgetProvider: (id: string) => MediaProvider | undefined,\n): Promise<MediaValue | null> {\n\t// Internal media URL — try to resolve via local provider\n\tif (url.startsWith(INTERNAL_MEDIA_PREFIX)) {\n\t\treturn resolveInternalUrl(url, getProvider);\n\t}\n\n\t// External HTTP(S) URL\n\tif (URL_PATTERN.test(url)) {\n\t\treturn Promise.resolve({\n\t\t\tprovider: \"external\",\n\t\t\tid: \"\",\n\t\t\tsrc: url,\n\t\t});\n\t}\n\n\tconst localMedia = await resolveLocalId(url, getProvider);\n\tif (localMedia) return localMedia;\n\n\t// Unrecognized string — preserve legacy behavior and treat as external\n\treturn {\n\t\tprovider: \"external\",\n\t\tid: \"\",\n\t\tsrc: url,\n\t};\n}\n\nasync function resolveInternalUrl(\n\turl: string,\n\tgetProvider: (id: string) => MediaProvider | undefined,\n): Promise<MediaValue> {\n\tconst storageKey = url.slice(INTERNAL_MEDIA_PREFIX.length);\n\tconst localProvider = getProvider(\"local\");\n\n\tif (!localProvider?.get) {\n\t\treturn { provider: \"external\", id: \"\", src: url };\n\t}\n\n\tlet item: MediaProviderItem | null;\n\ttry {\n\t\titem = await localProvider.get(storageKey);\n\t} catch {\n\t\treturn { provider: \"external\", id: \"\", src: url };\n\t}\n\n\tif (!item) {\n\t\treturn { provider: \"external\", id: \"\", src: url };\n\t}\n\n\tconst focalPoint = normalizeFocalPoint(item.focalX, item.focalY);\n\treturn {\n\t\tprovider: \"local\",\n\t\tid: item.id,\n\t\tfilename: item.filename,\n\t\tmimeType: item.mimeType,\n\t\twidth: item.width,\n\t\theight: item.height,\n\t\t...focalPoint,\n\t\tblurhash: item.blurhash,\n\t\tdominantColor: item.dominantColor,\n\t\talt: item.alt,\n\t\tmeta: item.meta,\n\t};\n}\n\nasync function resolveLocalId(\n\tid: string,\n\tgetProvider: (id: string) => MediaProvider | undefined,\n): Promise<MediaValue | null> {\n\tconst localProvider = getProvider(\"local\");\n\n\tif (!localProvider?.get) return null;\n\n\tlet item: MediaProviderItem | null;\n\ttry {\n\t\titem = await localProvider.get(id);\n\t} catch {\n\t\treturn null;\n\t}\n\n\tif (!item) return null;\n\n\tconst focalPoint = normalizeFocalPoint(item.focalX, item.focalY);\n\treturn {\n\t\tprovider: \"local\",\n\t\tid: item.id,\n\t\tfilename: item.filename,\n\t\tmimeType: item.mimeType,\n\t\twidth: item.width,\n\t\theight: item.height,\n\t\t...focalPoint,\n\t\tblurhash: item.blurhash,\n\t\tdominantColor: item.dominantColor,\n\t\talt: item.alt,\n\t\tmeta: item.meta,\n\t};\n}\n\n/**\n * Merge provider data into an existing MediaValue, preserving caller-supplied fields.\n * Caller `alt` takes priority over provider `alt` (per-usage, not per-image).\n */\nfunction mergeProviderData(existing: MediaValue, item: MediaProviderItem): MediaValue {\n\tconst result = { ...existing };\n\n\t// Fill missing dimensions\n\tif (result.width == null && item.width != null) result.width = item.width;\n\tif (result.height == null && item.height != null) result.height = item.height;\n\n\t// Fill missing LQIP placeholders (immutable facts of the bytes; caller wins)\n\tif (result.blurhash == null && item.blurhash != null) result.blurhash = item.blurhash;\n\tif (result.dominantColor == null && item.dominantColor != null) {\n\t\tresult.dominantColor = item.dominantColor;\n\t}\n\n\t// Fill missing file info\n\tif (!result.filename && item.filename) result.filename = item.filename;\n\tif (!result.mimeType && item.mimeType) result.mimeType = item.mimeType;\n\n\t// Fill missing alt (provider alt is fallback, not override)\n\tif (!result.alt && item.alt) result.alt = item.alt;\n\n\t// Fill missing meta (merge, don't replace)\n\tif (item.meta) {\n\t\tresult.meta = { ...item.meta, ...result.meta };\n\t}\n\n\treturn result;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Extract known MediaValue fields from a runtime-checked record.\n * Avoids unsafe `as MediaValue` cast by reading each property explicitly.\n */\nfunction recordToMediaValue(obj: Record<string, unknown>): MediaValue {\n\tconst result: MediaValue = {\n\t\tid: typeof obj.id === \"string\" ? obj.id : \"\",\n\t};\n\tif (typeof obj.provider === \"string\") result.provider = obj.provider;\n\tif (typeof obj.src === \"string\") result.src = obj.src;\n\tif (typeof obj.previewUrl === \"string\") result.previewUrl = obj.previewUrl;\n\tif (typeof obj.filename === \"string\") result.filename = obj.filename;\n\tif (typeof obj.mimeType === \"string\") result.mimeType = obj.mimeType;\n\tif (typeof obj.width === \"number\") result.width = obj.width;\n\tif (typeof obj.height === \"number\") result.height = obj.height;\n\tconst focalPoint = normalizeFocalPoint(obj.focalX, obj.focalY);\n\tif (focalPoint) {\n\t\tresult.focalX = focalPoint.focalX;\n\t\tresult.focalY = focalPoint.focalY;\n\t}\n\tif (typeof obj.blurhash === \"string\") result.blurhash = obj.blurhash;\n\tif (typeof obj.dominantColor === \"string\") result.dominantColor = obj.dominantColor;\n\tif (typeof obj.alt === \"string\") result.alt = obj.alt;\n\tif (isRecord(obj.meta)) result.meta = obj.meta;\n\treturn result;\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { t as withTransaction } from "./transaction-NQj4VJ7Z.mjs";
2
- import { a as hashApiToken, o as isValidScope, r as generatePrefixedToken, t as TOKEN_PREFIXES } from "./api-tokens-CyTsLszZ.mjs";
3
- import { c as validateRedirectUri, o as lookupOAuthClient, s as validateClientRedirectUri } from "./oauth-clients-C_DNqtRu.mjs";
4
- import { t as lookupUserRoleAndStatus } from "./oauth-user-lookup-BxfZizdk.mjs";
1
+ import { t as withTransaction } from "./transaction-0KOpOyMu.mjs";
2
+ import { a as hashApiToken, o as isValidScope, r as generatePrefixedToken, t as TOKEN_PREFIXES } from "./api-tokens-B5NHLNUN.mjs";
3
+ import { c as validateRedirectUri, o as lookupOAuthClient, s as validateClientRedirectUri } from "./oauth-clients-Bxnoe_67.mjs";
4
+ import { t as lookupUserRoleAndStatus } from "./oauth-user-lookup-wlGYSeqX.mjs";
5
5
  import { clampScopes, computeS256Challenge, secureCompare } from "@emdash-cms/auth";
6
6
  import { generateCodeVerifier } from "arctic";
7
7
 
@@ -271,4 +271,4 @@ function buildDeniedRedirect(redirectUri, state) {
271
271
 
272
272
  //#endregion
273
273
  export { handleAuthorizationApproval as n, handleAuthorizationCodeExchange as r, buildDeniedRedirect as t };
274
- //# sourceMappingURL=oauth-authorization-BXO1MKej.mjs.map
274
+ //# sourceMappingURL=oauth-authorization-Dpsb-fMJ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-authorization-BXO1MKej.mjs","names":[],"sources":["../src/api/handlers/oauth-authorization.ts"],"sourcesContent":["/**\n * OAuth 2.1 Authorization Code + PKCE handlers.\n *\n * Implements the server side of the authorization code grant for MCP clients\n * (Claude Desktop, VS Code, etc.) per the MCP authorization spec (draft).\n *\n * Uses arctic for PKCE challenge generation and @emdash-cms/auth for token\n * utilities. Token infrastructure is shared with the device flow.\n */\n\nimport { clampScopes, computeS256Challenge, secureCompare } from \"@emdash-cms/auth\";\nimport type { RoleLevel } from \"@emdash-cms/auth\";\nimport { generateCodeVerifier } from \"arctic\";\nimport type { Kysely } from \"kysely\";\n\nimport {\n\tgeneratePrefixedToken,\n\thashApiToken,\n\tisValidScope,\n\tTOKEN_PREFIXES,\n} from \"../../auth/api-tokens.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { validateRedirectUri } from \"../oauth/redirect-uri.js\";\nimport type { ApiResult } from \"../types.js\";\nimport { lookupOAuthClient, validateClientRedirectUri } from \"./oauth-clients.js\";\nimport { lookupUserRoleAndStatus } from \"./oauth-user-lookup.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Authorization codes expire after 10 minutes (RFC 6749 §4.1.2 recommends short-lived) */\nconst AUTH_CODE_TTL_SECONDS = 10 * 60;\n\n/** Access token TTL: 1 hour */\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** Refresh token TTL: 90 days */\nconst REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AuthorizationParams {\n\tresponse_type: string;\n\tclient_id: string;\n\tredirect_uri: string;\n\tscope?: string;\n\tstate?: string;\n\tcode_challenge: string;\n\tcode_challenge_method: string;\n\tresource?: string;\n}\n\nexport interface TokenExchangeParams {\n\tgrant_type: string;\n\tcode: string;\n\tredirect_uri: string;\n\tclient_id: string;\n\tcode_verifier: string;\n\tresource?: string;\n}\n\nexport interface TokenResponse {\n\taccess_token: string;\n\trefresh_token: string;\n\ttoken_type: \"Bearer\";\n\texpires_in: number;\n\tscope: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction expiresAt(seconds: number): string {\n\treturn new Date(Date.now() + seconds * 1000).toISOString();\n}\n\nexport { validateRedirectUri };\n\n/**\n * Validate and normalize scopes. Returns validated scope list.\n */\nfunction normalizeScopes(requested?: string): string[] {\n\tif (!requested) return [];\n\n\tconst scopes = requested.split(\" \").filter(Boolean).filter(isValidScope);\n\n\treturn scopes;\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Process an authorization request after the user approves consent.\n *\n * Generates an authorization code, stores it with the PKCE challenge,\n * and returns the redirect URL with the code appended.\n *\n * Scopes are clamped to the user's role to prevent scope escalation.\n */\nexport async function handleAuthorizationApproval(\n\tdb: Kysely<Database>,\n\tuserId: string,\n\tuserRole: RoleLevel,\n\tparams: AuthorizationParams,\n): Promise<ApiResult<{ redirect_url: string }>> {\n\ttry {\n\t\t// Validate response_type\n\t\tif (params.response_type !== \"code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"UNSUPPORTED_RESPONSE_TYPE\",\n\t\t\t\t\tmessage: \"Only response_type=code is supported\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate redirect_uri scheme/host (basic security check)\n\t\tconst uriError = validateRedirectUri(params.redirect_uri);\n\t\tif (uriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REDIRECT_URI\", message: uriError },\n\t\t\t};\n\t\t}\n\n\t\t// Look up the registered OAuth client\n\t\tconst client = await lookupOAuthClient(db, params.client_id);\n\t\tif (!client) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_CLIENT\",\n\t\t\t\t\tmessage: \"Unknown client_id\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate redirect_uri against client's registered URIs\n\t\tconst clientUriError = validateClientRedirectUri(params.redirect_uri, client.redirectUris);\n\t\tif (clientUriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REDIRECT_URI\", message: clientUriError },\n\t\t\t};\n\t\t}\n\n\t\t// Validate code_challenge_method\n\t\tif (params.code_challenge_method !== \"S256\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_REQUEST\",\n\t\t\t\t\tmessage: \"Only S256 code_challenge_method is supported\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate code_challenge is present\n\t\tif (!params.code_challenge) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REQUEST\", message: \"code_challenge is required\" },\n\t\t\t};\n\t\t}\n\n\t\t// Validate scopes, then clamp to user's role\n\t\tconst userScopes = clampScopes(normalizeScopes(params.scope), userRole);\n\n\t\t// SEC-41: Intersect with client's registered scopes (if restricted).\n\t\t// A client registered with scopes: [\"content:read\"] should never receive\n\t\t// admin or schema:write, regardless of the approving user's role.\n\t\tconst clientScopes = client.scopes;\n\t\tconst scopes = clientScopes?.length\n\t\t\t? userScopes.filter((s: string) => clientScopes.includes(s))\n\t\t\t: userScopes;\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_SCOPE\", message: \"No valid scopes requested\" },\n\t\t\t};\n\t\t}\n\n\t\t// Generate authorization code (high entropy, base64url)\n\t\tconst code = generateCodeVerifier(); // 32 bytes random, base64url\n\t\tconst codeHash = hashApiToken(code);\n\n\t\t// Store the authorization code\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_authorization_codes\")\n\t\t\t.values({\n\t\t\t\tcode_hash: codeHash,\n\t\t\t\tclient_id: params.client_id,\n\t\t\t\tredirect_uri: params.redirect_uri,\n\t\t\t\tuser_id: userId,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tcode_challenge: params.code_challenge,\n\t\t\t\tcode_challenge_method: params.code_challenge_method,\n\t\t\t\tresource: params.resource ?? null,\n\t\t\t\texpires_at: expiresAt(AUTH_CODE_TTL_SECONDS),\n\t\t\t})\n\t\t\t.execute();\n\n\t\t// Build the redirect URL\n\t\tconst redirectUrl = new URL(params.redirect_uri);\n\t\tredirectUrl.searchParams.set(\"code\", code);\n\t\tif (params.state) {\n\t\t\tredirectUrl.searchParams.set(\"state\", params.state);\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { redirect_url: redirectUrl.toString() },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Authorization error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"AUTHORIZATION_ERROR\",\n\t\t\t\tmessage: \"Failed to process authorization\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Exchange an authorization code for access + refresh tokens.\n *\n * Validates the code, verifies PKCE, and issues tokens using the same\n * infrastructure as the device flow (ec_oat_*, ec_ort_*).\n */\nexport async function handleAuthorizationCodeExchange(\n\tdb: Kysely<Database>,\n\tparams: TokenExchangeParams,\n): Promise<ApiResult<TokenResponse>> {\n\ttry {\n\t\t// Validate grant_type\n\t\tif (params.grant_type !== \"authorization_code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"unsupported_grant_type\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\t// SEC-39: Atomically consume the authorization code using DELETE...RETURNING.\n\t\t// This prevents TOCTOU double-exchange: two concurrent requests with the\n\t\t// same code will race on the DELETE, and only one will get a row back.\n\t\tconst codeHash = hashApiToken(params.code);\n\n\t\tconst row = await db\n\t\t\t.deleteFrom(\"_emdash_authorization_codes\")\n\t\t\t.where(\"code_hash\", \"=\", codeHash)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"Invalid authorization code\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < new Date()) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"Authorization code expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify redirect_uri matches exactly\n\t\tif (row.redirect_uri !== params.redirect_uri) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"redirect_uri mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify client_id matches\n\t\tif (row.client_id !== params.client_id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"client_id mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// PKCE verification: SHA256(code_verifier) must match stored code_challenge\n\t\t// Use constant-time comparison to prevent timing side-channels\n\t\tconst derivedChallenge = computeS256Challenge(params.code_verifier);\n\t\tif (!secureCompare(derivedChallenge, row.code_challenge)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"PKCE verification failed\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify resource matches (if stored)\n\t\tif (row.resource && params.resource && row.resource !== params.resource) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"resource mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// Revalidate user role before issuing tokens (same pattern as handleTokenRefresh).\n\t\t// The user's role may have changed since the authorization code was issued.\n\t\tconst userInfo = await lookupUserRoleAndStatus(db, row.user_id);\n\t\tif (!userInfo) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"User not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (userInfo.disabled) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"User account is disabled\" },\n\t\t\t};\n\t\t}\n\n\t\t// Re-clamp scopes against the user's current role\n\t\tconst storedScopes = JSON.parse(row.scopes) as string[];\n\t\tlet scopes = clampScopes(storedScopes, userInfo.role);\n\n\t\t// Intersect with client's registered scopes (if restricted)\n\t\tconst client = await lookupOAuthClient(db, row.client_id);\n\t\tif (client?.scopes?.length) {\n\t\t\tscopes = scopes.filter((s: string) => client.scopes!.includes(s));\n\t\t}\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"invalid_grant\",\n\t\t\t\t\tmessage: \"User role no longer supports any of the requested scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Issue tokens (same as device flow)\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\n\t\tconst refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);\n\t\tconst refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);\n\n\t\t// Atomically store both tokens in a transaction\n\t\tawait withTransaction(db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\t\ttoken_type: \"access\",\n\t\t\t\t\tuser_id: row.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"mcp\",\n\t\t\t\t\texpires_at: accessExpires,\n\t\t\t\t\trefresh_token_hash: refreshToken.hash,\n\t\t\t\t\tclient_id: row.client_id,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: refreshToken.hash,\n\t\t\t\t\ttoken_type: \"refresh\",\n\t\t\t\t\tuser_id: row.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"mcp\",\n\t\t\t\t\texpires_at: refreshExpires,\n\t\t\t\t\trefresh_token_hash: null,\n\t\t\t\t\tclient_id: row.client_id,\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: refreshToken.raw,\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Token exchange error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_EXCHANGE_ERROR\",\n\t\t\t\tmessage: \"Failed to exchange authorization code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Build the authorization denied redirect URL.\n */\nexport function buildDeniedRedirect(redirectUri: string, state?: string): string {\n\tconst url = new URL(redirectUri);\n\turl.searchParams.set(\"error\", \"access_denied\");\n\turl.searchParams.set(\"error_description\", \"The user denied the authorization request\");\n\tif (state) {\n\t\turl.searchParams.set(\"state\", state);\n\t}\n\treturn url.toString();\n}\n\n/**\n * Clean up expired authorization codes.\n */\nexport async function cleanupExpiredAuthorizationCodes(db: Kysely<Database>): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_emdash_authorization_codes\")\n\t\t.where(\"expires_at\", \"<\", new Date().toISOString())\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiCA,MAAM,wBAAwB;;AAG9B,MAAM,2BAA2B;;AAGjC,MAAM,4BAA4B,OAAU,KAAK;AAsCjD,SAAS,UAAU,SAAyB;AAC3C,QAAO,IAAI,KAAK,KAAK,KAAK,GAAG,UAAU,IAAK,CAAC,aAAa;;;;;AAQ3D,SAAS,gBAAgB,WAA8B;AACtD,KAAI,CAAC,UAAW,QAAO,EAAE;AAIzB,QAFe,UAAU,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,aAAa;;;;;;;;;;AAiBzE,eAAsB,4BACrB,IACA,QACA,UACA,QAC+C;AAC/C,KAAI;AAEH,MAAI,OAAO,kBAAkB,OAC5B,QAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;EAIF,MAAM,WAAW,oBAAoB,OAAO,aAAa;AACzD,MAAI,SACH,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAwB,SAAS;IAAU;GAC1D;EAIF,MAAM,SAAS,MAAM,kBAAkB,IAAI,OAAO,UAAU;AAC5D,MAAI,CAAC,OACJ,QAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;EAIF,MAAM,iBAAiB,0BAA0B,OAAO,cAAc,OAAO,aAAa;AAC1F,MAAI,eACH,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAwB,SAAS;IAAgB;GAChE;AAIF,MAAI,OAAO,0BAA0B,OACpC,QAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;AAIF,MAAI,CAAC,OAAO,eACX,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAmB,SAAS;IAA8B;GACzE;EAIF,MAAM,aAAa,YAAY,gBAAgB,OAAO,MAAM,EAAE,SAAS;EAKvE,MAAM,eAAe,OAAO;EAC5B,MAAM,SAAS,cAAc,SAC1B,WAAW,QAAQ,MAAc,aAAa,SAAS,EAAE,CAAC,GAC1D;AAEH,MAAI,OAAO,WAAW,EACrB,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAA6B;GACtE;EAIF,MAAM,OAAO,sBAAsB;EACnC,MAAM,WAAW,aAAa,KAAK;AAGnC,QAAM,GACJ,WAAW,8BAA8B,CACzC,OAAO;GACP,WAAW;GACX,WAAW,OAAO;GAClB,cAAc,OAAO;GACrB,SAAS;GACT,QAAQ,KAAK,UAAU,OAAO;GAC9B,gBAAgB,OAAO;GACvB,uBAAuB,OAAO;GAC9B,UAAU,OAAO,YAAY;GAC7B,YAAY,UAAU,sBAAsB;GAC5C,CAAC,CACD,SAAS;EAGX,MAAM,cAAc,IAAI,IAAI,OAAO,aAAa;AAChD,cAAY,aAAa,IAAI,QAAQ,KAAK;AAC1C,MAAI,OAAO,MACV,aAAY,aAAa,IAAI,SAAS,OAAO,MAAM;AAGpD,SAAO;GACN,SAAS;GACT,MAAM,EAAE,cAAc,YAAY,UAAU,EAAE;GAC9C;UACO,OAAO;AACf,UAAQ,MAAM,wBAAwB,MAAM;AAC5C,SAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;;;;;;;;;AAUH,eAAsB,gCACrB,IACA,QACoC;AACpC,KAAI;AAEH,MAAI,OAAO,eAAe,qBACzB,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAA0B,SAAS;IAAsB;GACxE;EAMF,MAAM,WAAW,aAAa,OAAO,KAAK;EAE1C,MAAM,MAAM,MAAM,GAChB,WAAW,8BAA8B,CACzC,MAAM,aAAa,KAAK,SAAS,CACjC,cAAc,CACd,kBAAkB;AAEpB,MAAI,CAAC,IACJ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAA8B;GACvE;AAIF,MAAI,IAAI,KAAK,IAAI,WAAW,mBAAG,IAAI,MAAM,CACxC,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAA8B;GACvE;AAIF,MAAI,IAAI,iBAAiB,OAAO,aAC/B,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAyB;GAClE;AAIF,MAAI,IAAI,cAAc,OAAO,UAC5B,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAsB;GAC/D;AAMF,MAAI,CAAC,cADoB,qBAAqB,OAAO,cAAc,EAC9B,IAAI,eAAe,CACvD,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAA4B;GACrE;AAIF,MAAI,IAAI,YAAY,OAAO,YAAY,IAAI,aAAa,OAAO,SAC9D,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAqB;GAC9D;EAKF,MAAM,WAAW,MAAM,wBAAwB,IAAI,IAAI,QAAQ;AAC/D,MAAI,CAAC,SACJ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAAkB;GAC3D;AAGF,MAAI,SAAS,SACZ,QAAO;GACN,SAAS;GACT,OAAO;IAAE,MAAM;IAAiB,SAAS;IAA4B;GACrE;EAKF,IAAI,SAAS,YADQ,KAAK,MAAM,IAAI,OAAO,EACJ,SAAS,KAAK;EAGrD,MAAM,SAAS,MAAM,kBAAkB,IAAI,IAAI,UAAU;AACzD,MAAI,QAAQ,QAAQ,OACnB,UAAS,OAAO,QAAQ,MAAc,OAAO,OAAQ,SAAS,EAAE,CAAC;AAGlE,MAAI,OAAO,WAAW,EACrB,QAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;EAIF,MAAM,cAAc,sBAAsB,eAAe,aAAa;EACtE,MAAM,gBAAgB,UAAU,yBAAyB;EAEzD,MAAM,eAAe,sBAAsB,eAAe,cAAc;EACxE,MAAM,iBAAiB,UAAU,0BAA0B;AAG3D,QAAM,gBAAgB,IAAI,OAAO,QAAQ;AACxC,SAAM,IACJ,WAAW,uBAAuB,CAClC,OAAO;IACP,YAAY,YAAY;IACxB,YAAY;IACZ,SAAS,IAAI;IACb,QAAQ,KAAK,UAAU,OAAO;IAC9B,aAAa;IACb,YAAY;IACZ,oBAAoB,aAAa;IACjC,WAAW,IAAI;IACf,CAAC,CACD,SAAS;AAEX,SAAM,IACJ,WAAW,uBAAuB,CAClC,OAAO;IACP,YAAY,aAAa;IACzB,YAAY;IACZ,SAAS,IAAI;IACb,QAAQ,KAAK,UAAU,OAAO;IAC9B,aAAa;IACb,YAAY;IACZ,oBAAoB;IACpB,WAAW,IAAI;IACf,CAAC,CACD,SAAS;IACV;AAEF,SAAO;GACN,SAAS;GACT,MAAM;IACL,cAAc,YAAY;IAC1B,eAAe,aAAa;IAC5B,YAAY;IACZ,YAAY;IACZ,OAAO,OAAO,KAAK,IAAI;IACvB;GACD;UACO,OAAO;AACf,UAAQ,MAAM,yBAAyB,MAAM;AAC7C,SAAO;GACN,SAAS;GACT,OAAO;IACN,MAAM;IACN,SAAS;IACT;GACD;;;;;;AAOH,SAAgB,oBAAoB,aAAqB,OAAwB;CAChF,MAAM,MAAM,IAAI,IAAI,YAAY;AAChC,KAAI,aAAa,IAAI,SAAS,gBAAgB;AAC9C,KAAI,aAAa,IAAI,qBAAqB,4CAA4C;AACtF,KAAI,MACH,KAAI,aAAa,IAAI,SAAS,MAAM;AAErC,QAAO,IAAI,UAAU"}
1
+ {"version":3,"file":"oauth-authorization-Dpsb-fMJ.mjs","names":[],"sources":["../src/api/handlers/oauth-authorization.ts"],"sourcesContent":["/**\n * OAuth 2.1 Authorization Code + PKCE handlers.\n *\n * Implements the server side of the authorization code grant for MCP clients\n * (Claude Desktop, VS Code, etc.) per the MCP authorization spec (draft).\n *\n * Uses arctic for PKCE challenge generation and @emdash-cms/auth for token\n * utilities. Token infrastructure is shared with the device flow.\n */\n\nimport { clampScopes, computeS256Challenge, secureCompare } from \"@emdash-cms/auth\";\nimport type { RoleLevel } from \"@emdash-cms/auth\";\nimport { generateCodeVerifier } from \"arctic\";\nimport type { Kysely } from \"kysely\";\n\nimport {\n\tgeneratePrefixedToken,\n\thashApiToken,\n\tisValidScope,\n\tTOKEN_PREFIXES,\n} from \"../../auth/api-tokens.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { validateRedirectUri } from \"../oauth/redirect-uri.js\";\nimport type { ApiResult } from \"../types.js\";\nimport { lookupOAuthClient, validateClientRedirectUri } from \"./oauth-clients.js\";\nimport { lookupUserRoleAndStatus } from \"./oauth-user-lookup.js\";\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Authorization codes expire after 10 minutes (RFC 6749 §4.1.2 recommends short-lived) */\nconst AUTH_CODE_TTL_SECONDS = 10 * 60;\n\n/** Access token TTL: 1 hour */\nconst ACCESS_TOKEN_TTL_SECONDS = 60 * 60;\n\n/** Refresh token TTL: 90 days */\nconst REFRESH_TOKEN_TTL_SECONDS = 90 * 24 * 60 * 60;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AuthorizationParams {\n\tresponse_type: string;\n\tclient_id: string;\n\tredirect_uri: string;\n\tscope?: string;\n\tstate?: string;\n\tcode_challenge: string;\n\tcode_challenge_method: string;\n\tresource?: string;\n}\n\nexport interface TokenExchangeParams {\n\tgrant_type: string;\n\tcode: string;\n\tredirect_uri: string;\n\tclient_id: string;\n\tcode_verifier: string;\n\tresource?: string;\n}\n\nexport interface TokenResponse {\n\taccess_token: string;\n\trefresh_token: string;\n\ttoken_type: \"Bearer\";\n\texpires_in: number;\n\tscope: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction expiresAt(seconds: number): string {\n\treturn new Date(Date.now() + seconds * 1000).toISOString();\n}\n\nexport { validateRedirectUri };\n\n/**\n * Validate and normalize scopes. Returns validated scope list.\n */\nfunction normalizeScopes(requested?: string): string[] {\n\tif (!requested) return [];\n\n\tconst scopes = requested.split(\" \").filter(Boolean).filter(isValidScope);\n\n\treturn scopes;\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Process an authorization request after the user approves consent.\n *\n * Generates an authorization code, stores it with the PKCE challenge,\n * and returns the redirect URL with the code appended.\n *\n * Scopes are clamped to the user's role to prevent scope escalation.\n */\nexport async function handleAuthorizationApproval(\n\tdb: Kysely<Database>,\n\tuserId: string,\n\tuserRole: RoleLevel,\n\tparams: AuthorizationParams,\n): Promise<ApiResult<{ redirect_url: string }>> {\n\ttry {\n\t\t// Validate response_type\n\t\tif (params.response_type !== \"code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"UNSUPPORTED_RESPONSE_TYPE\",\n\t\t\t\t\tmessage: \"Only response_type=code is supported\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate redirect_uri scheme/host (basic security check)\n\t\tconst uriError = validateRedirectUri(params.redirect_uri);\n\t\tif (uriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REDIRECT_URI\", message: uriError },\n\t\t\t};\n\t\t}\n\n\t\t// Look up the registered OAuth client\n\t\tconst client = await lookupOAuthClient(db, params.client_id);\n\t\tif (!client) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_CLIENT\",\n\t\t\t\t\tmessage: \"Unknown client_id\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate redirect_uri against client's registered URIs\n\t\tconst clientUriError = validateClientRedirectUri(params.redirect_uri, client.redirectUris);\n\t\tif (clientUriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REDIRECT_URI\", message: clientUriError },\n\t\t\t};\n\t\t}\n\n\t\t// Validate code_challenge_method\n\t\tif (params.code_challenge_method !== \"S256\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INVALID_REQUEST\",\n\t\t\t\t\tmessage: \"Only S256 code_challenge_method is supported\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Validate code_challenge is present\n\t\tif (!params.code_challenge) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_REQUEST\", message: \"code_challenge is required\" },\n\t\t\t};\n\t\t}\n\n\t\t// Validate scopes, then clamp to user's role\n\t\tconst userScopes = clampScopes(normalizeScopes(params.scope), userRole);\n\n\t\t// SEC-41: Intersect with client's registered scopes (if restricted).\n\t\t// A client registered with scopes: [\"content:read\"] should never receive\n\t\t// admin or schema:write, regardless of the approving user's role.\n\t\tconst clientScopes = client.scopes;\n\t\tconst scopes = clientScopes?.length\n\t\t\t? userScopes.filter((s: string) => clientScopes.includes(s))\n\t\t\t: userScopes;\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_SCOPE\", message: \"No valid scopes requested\" },\n\t\t\t};\n\t\t}\n\n\t\t// Generate authorization code (high entropy, base64url)\n\t\tconst code = generateCodeVerifier(); // 32 bytes random, base64url\n\t\tconst codeHash = hashApiToken(code);\n\n\t\t// Store the authorization code\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_authorization_codes\")\n\t\t\t.values({\n\t\t\t\tcode_hash: codeHash,\n\t\t\t\tclient_id: params.client_id,\n\t\t\t\tredirect_uri: params.redirect_uri,\n\t\t\t\tuser_id: userId,\n\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\tcode_challenge: params.code_challenge,\n\t\t\t\tcode_challenge_method: params.code_challenge_method,\n\t\t\t\tresource: params.resource ?? null,\n\t\t\t\texpires_at: expiresAt(AUTH_CODE_TTL_SECONDS),\n\t\t\t})\n\t\t\t.execute();\n\n\t\t// Build the redirect URL\n\t\tconst redirectUrl = new URL(params.redirect_uri);\n\t\tredirectUrl.searchParams.set(\"code\", code);\n\t\tif (params.state) {\n\t\t\tredirectUrl.searchParams.set(\"state\", params.state);\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { redirect_url: redirectUrl.toString() },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Authorization error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"AUTHORIZATION_ERROR\",\n\t\t\t\tmessage: \"Failed to process authorization\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Exchange an authorization code for access + refresh tokens.\n *\n * Validates the code, verifies PKCE, and issues tokens using the same\n * infrastructure as the device flow (ec_oat_*, ec_ort_*).\n */\nexport async function handleAuthorizationCodeExchange(\n\tdb: Kysely<Database>,\n\tparams: TokenExchangeParams,\n): Promise<ApiResult<TokenResponse>> {\n\ttry {\n\t\t// Validate grant_type\n\t\tif (params.grant_type !== \"authorization_code\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"unsupported_grant_type\", message: \"Invalid grant_type\" },\n\t\t\t};\n\t\t}\n\n\t\t// SEC-39: Atomically consume the authorization code using DELETE...RETURNING.\n\t\t// This prevents TOCTOU double-exchange: two concurrent requests with the\n\t\t// same code will race on the DELETE, and only one will get a row back.\n\t\tconst codeHash = hashApiToken(params.code);\n\n\t\tconst row = await db\n\t\t\t.deleteFrom(\"_emdash_authorization_codes\")\n\t\t\t.where(\"code_hash\", \"=\", codeHash)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"Invalid authorization code\" },\n\t\t\t};\n\t\t}\n\n\t\t// Check expiry\n\t\tif (new Date(row.expires_at) < new Date()) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"Authorization code expired\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify redirect_uri matches exactly\n\t\tif (row.redirect_uri !== params.redirect_uri) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"redirect_uri mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify client_id matches\n\t\tif (row.client_id !== params.client_id) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"client_id mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// PKCE verification: SHA256(code_verifier) must match stored code_challenge\n\t\t// Use constant-time comparison to prevent timing side-channels\n\t\tconst derivedChallenge = computeS256Challenge(params.code_verifier);\n\t\tif (!secureCompare(derivedChallenge, row.code_challenge)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"PKCE verification failed\" },\n\t\t\t};\n\t\t}\n\n\t\t// Verify resource matches (if stored)\n\t\tif (row.resource && params.resource && row.resource !== params.resource) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"resource mismatch\" },\n\t\t\t};\n\t\t}\n\n\t\t// Revalidate user role before issuing tokens (same pattern as handleTokenRefresh).\n\t\t// The user's role may have changed since the authorization code was issued.\n\t\tconst userInfo = await lookupUserRoleAndStatus(db, row.user_id);\n\t\tif (!userInfo) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"User not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (userInfo.disabled) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"invalid_grant\", message: \"User account is disabled\" },\n\t\t\t};\n\t\t}\n\n\t\t// Re-clamp scopes against the user's current role\n\t\tconst storedScopes = JSON.parse(row.scopes) as string[];\n\t\tlet scopes = clampScopes(storedScopes, userInfo.role);\n\n\t\t// Intersect with client's registered scopes (if restricted)\n\t\tconst client = await lookupOAuthClient(db, row.client_id);\n\t\tif (client?.scopes?.length) {\n\t\t\tscopes = scopes.filter((s: string) => client.scopes!.includes(s));\n\t\t}\n\n\t\tif (scopes.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"invalid_grant\",\n\t\t\t\t\tmessage: \"User role no longer supports any of the requested scopes\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Issue tokens (same as device flow)\n\t\tconst accessToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_ACCESS);\n\t\tconst accessExpires = expiresAt(ACCESS_TOKEN_TTL_SECONDS);\n\n\t\tconst refreshToken = generatePrefixedToken(TOKEN_PREFIXES.OAUTH_REFRESH);\n\t\tconst refreshExpires = expiresAt(REFRESH_TOKEN_TTL_SECONDS);\n\n\t\t// Atomically store both tokens in a transaction\n\t\tawait withTransaction(db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: accessToken.hash,\n\t\t\t\t\ttoken_type: \"access\",\n\t\t\t\t\tuser_id: row.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"mcp\",\n\t\t\t\t\texpires_at: accessExpires,\n\t\t\t\t\trefresh_token_hash: refreshToken.hash,\n\t\t\t\t\tclient_id: row.client_id,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_oauth_tokens\")\n\t\t\t\t.values({\n\t\t\t\t\ttoken_hash: refreshToken.hash,\n\t\t\t\t\ttoken_type: \"refresh\",\n\t\t\t\t\tuser_id: row.user_id,\n\t\t\t\t\tscopes: JSON.stringify(scopes),\n\t\t\t\t\tclient_type: \"mcp\",\n\t\t\t\t\texpires_at: refreshExpires,\n\t\t\t\t\trefresh_token_hash: null,\n\t\t\t\t\tclient_id: row.client_id,\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\taccess_token: accessToken.raw,\n\t\t\t\trefresh_token: refreshToken.raw,\n\t\t\t\ttoken_type: \"Bearer\",\n\t\t\t\texpires_in: ACCESS_TOKEN_TTL_SECONDS,\n\t\t\t\tscope: scopes.join(\" \"),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Token exchange error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"TOKEN_EXCHANGE_ERROR\",\n\t\t\t\tmessage: \"Failed to exchange authorization code\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Build the authorization denied redirect URL.\n */\nexport function buildDeniedRedirect(redirectUri: string, state?: string): string {\n\tconst url = new URL(redirectUri);\n\turl.searchParams.set(\"error\", \"access_denied\");\n\turl.searchParams.set(\"error_description\", \"The user denied the authorization request\");\n\tif (state) {\n\t\turl.searchParams.set(\"state\", state);\n\t}\n\treturn url.toString();\n}\n\n/**\n * Clean up expired authorization codes.\n */\nexport async function cleanupExpiredAuthorizationCodes(db: Kysely<Database>): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_emdash_authorization_codes\")\n\t\t.where(\"expires_at\", \"<\", new Date().toISOString())\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows);\n}\n"],"mappings":"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"}
@@ -263,4 +263,4 @@ function validateClientRedirectUri(redirectUri, allowedUris) {
263
263
 
264
264
  //#endregion
265
265
  export { handleOAuthClientUpdate as a, validateRedirectUri as c, handleOAuthClientList as i, handleOAuthClientDelete as n, lookupOAuthClient as o, handleOAuthClientGet as r, validateClientRedirectUri as s, handleOAuthClientCreate as t };
266
- //# sourceMappingURL=oauth-clients-C_DNqtRu.mjs.map
266
+ //# sourceMappingURL=oauth-clients-Bxnoe_67.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-clients-C_DNqtRu.mjs","names":[],"sources":["../src/api/oauth/redirect-uri.ts","../src/api/handlers/oauth-clients.ts"],"sourcesContent":["/**\n * Validate a redirect URI per OAuth 2.1 security requirements.\n *\n * Allows localhost / loopback redirect URIs over HTTP for native clients,\n * and any HTTPS URL for web-based flows.\n */\nexport function validateRedirectUri(uri: string): string | null {\n\ttry {\n\t\tconst url = new URL(uri);\n\n\t\t// Reject protocol-relative URLs\n\t\tif (uri.startsWith(\"//\")) {\n\t\t\treturn \"Protocol-relative redirect URIs are not allowed\";\n\t\t}\n\n\t\t// Allow localhost/loopback over HTTP (for desktop MCP clients)\n\t\tif (url.protocol === \"http:\") {\n\t\t\tconst host = url.hostname;\n\t\t\tif (host === \"127.0.0.1\" || host === \"localhost\" || host === \"[::1]\") {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn \"HTTP redirect URIs are only allowed for localhost\";\n\t\t}\n\n\t\t// Allow HTTPS\n\t\tif (url.protocol === \"https:\") {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn `Unsupported redirect URI scheme: ${url.protocol}`;\n\t} catch {\n\t\treturn \"Invalid redirect URI\";\n\t}\n}\n","/**\n * OAuth client management handlers.\n *\n * CRUD operations for registered OAuth clients. Each client has a set\n * of pre-registered redirect URIs. The authorization endpoint rejects\n * any redirect_uri not in the client's registered set.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateRedirectUri } from \"../oauth/redirect-uri.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Parse a JSON string column into a typed value. */\nfunction parseJsonColumn<T>(value: string): T {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown, callers provide the expected shape\n\treturn JSON.parse(value) as T;\n}\n\nfunction validateRegisteredRedirectUris(redirectUris: string[]): string | null {\n\tfor (const redirectUri of redirectUris) {\n\t\tconst error = validateRedirectUri(redirectUri);\n\t\tif (error) {\n\t\t\treturn `Invalid redirect URI: ${error}`;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthClientInfo {\n\tid: string;\n\tname: string;\n\tredirectUris: string[];\n\tscopes: string[] | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new OAuth client.\n */\nexport async function handleOAuthClientCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tid: string;\n\t\tname: string;\n\t\tredirectUris: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tif (input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\tif (redirectUriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Check for duplicate client ID\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"id\", \"=\", input.id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"CONFLICT\", message: \"OAuth client with this ID already exists\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_clients\")\n\t\t\t.values({\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirect_uris: JSON.stringify(input.redirectUris),\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirectUris: input.redirectUris,\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? input.scopes : null,\n\t\t\t\tcreatedAt: now,\n\t\t\t\tupdatedAt: now,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_CREATE_ERROR\",\n\t\t\t\tmessage: \"Failed to create OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * List all registered OAuth clients.\n */\nexport async function handleOAuthClientList(\n\tdb: Kysely<Database>,\n): Promise<ApiResult<{ items: OAuthClientInfo[] }>> {\n\ttry {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.execute();\n\n\t\tconst items: OAuthClientInfo[] = rows.map((row) => ({\n\t\t\tid: row.id,\n\t\t\tname: row.name,\n\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tupdatedAt: row.updated_at,\n\t\t}));\n\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list OAuth clients\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get a single OAuth client by ID.\n */\nexport async function handleOAuthClientGet(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\t\tcreatedAt: row.created_at,\n\t\t\t\tupdatedAt: row.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Update an OAuth client.\n */\nexport async function handleOAuthClientUpdate(\n\tdb: Kysely<Database>,\n\tclientId: string,\n\tinput: {\n\t\tname?: string;\n\t\tredirectUris?: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined && input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\t\tif (redirectUriError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst updates: Record<string, string | null> = {\n\t\t\tupdated_at: new Date().toISOString(),\n\t\t};\n\n\t\tif (input.name !== undefined) {\n\t\t\tupdates.name = input.name;\n\t\t}\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tupdates.redirect_uris = JSON.stringify(input.redirectUris);\n\t\t}\n\t\tif (input.scopes !== undefined) {\n\t\t\tupdates.scopes =\n\t\t\t\tinput.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null;\n\t\t}\n\n\t\tawait db.updateTable(\"_emdash_oauth_clients\").set(updates).where(\"id\", \"=\", clientId).execute();\n\n\t\t// Fetch the updated row\n\t\tconst updated = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found after update\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: updated.id,\n\t\t\t\tname: updated.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(updated.redirect_uris),\n\t\t\t\tscopes: updated.scopes ? parseJsonColumn<string[]>(updated.scopes) : null,\n\t\t\t\tcreatedAt: updated.created_at,\n\t\t\t\tupdatedAt: updated.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_UPDATE_ERROR\",\n\t\t\t\tmessage: \"Failed to update OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Delete an OAuth client.\n */\nexport async function handleOAuthClientDelete(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst result = await db\n\t\t\t.deleteFrom(\"_emdash_oauth_clients\")\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (result.numDeletedRows === 0n) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_DELETE_ERROR\",\n\t\t\t\tmessage: \"Failed to delete OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Lookup helpers (used by authorization handler)\n// ---------------------------------------------------------------------------\n\n/**\n * Look up a registered OAuth client by ID.\n * Returns the client's redirect URIs or null if the client is not registered.\n */\nexport async function lookupOAuthClient(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<{ redirectUris: string[]; scopes: string[] | null } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t.select([\"redirect_uris\", \"scopes\"])\n\t\t.where(\"id\", \"=\", clientId)\n\t\t.executeTakeFirst();\n\n\tif (!row) return null;\n\n\treturn {\n\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t};\n}\n\n/**\n * Validate that a redirect URI is in the client's registered set.\n *\n * Comparison is exact string match (per RFC 6749 §3.1.2.3).\n * Returns null if valid, or an error message if not.\n */\nexport function validateClientRedirectUri(\n\tredirectUri: string,\n\tallowedUris: string[],\n): string | null {\n\tif (allowedUris.includes(redirectUri)) {\n\t\treturn null; // OK\n\t}\n\treturn \"redirect_uri is not registered for this client\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"oauth-clients-Bxnoe_67.mjs","names":[],"sources":["../src/api/oauth/redirect-uri.ts","../src/api/handlers/oauth-clients.ts"],"sourcesContent":["/**\n * Validate a redirect URI per OAuth 2.1 security requirements.\n *\n * Allows localhost / loopback redirect URIs over HTTP for native clients,\n * and any HTTPS URL for web-based flows.\n */\nexport function validateRedirectUri(uri: string): string | null {\n\ttry {\n\t\tconst url = new URL(uri);\n\n\t\t// Reject protocol-relative URLs\n\t\tif (uri.startsWith(\"//\")) {\n\t\t\treturn \"Protocol-relative redirect URIs are not allowed\";\n\t\t}\n\n\t\t// Allow localhost/loopback over HTTP (for desktop MCP clients)\n\t\tif (url.protocol === \"http:\") {\n\t\t\tconst host = url.hostname;\n\t\t\tif (host === \"127.0.0.1\" || host === \"localhost\" || host === \"[::1]\") {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn \"HTTP redirect URIs are only allowed for localhost\";\n\t\t}\n\n\t\t// Allow HTTPS\n\t\tif (url.protocol === \"https:\") {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn `Unsupported redirect URI scheme: ${url.protocol}`;\n\t} catch {\n\t\treturn \"Invalid redirect URI\";\n\t}\n}\n","/**\n * OAuth client management handlers.\n *\n * CRUD operations for registered OAuth clients. Each client has a set\n * of pre-registered redirect URIs. The authorization endpoint rejects\n * any redirect_uri not in the client's registered set.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validateRedirectUri } from \"../oauth/redirect-uri.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Parse a JSON string column into a typed value. */\nfunction parseJsonColumn<T>(value: string): T {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns unknown, callers provide the expected shape\n\treturn JSON.parse(value) as T;\n}\n\nfunction validateRegisteredRedirectUris(redirectUris: string[]): string | null {\n\tfor (const redirectUri of redirectUris) {\n\t\tconst error = validateRedirectUri(redirectUri);\n\t\tif (error) {\n\t\t\treturn `Invalid redirect URI: ${error}`;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthClientInfo {\n\tid: string;\n\tname: string;\n\tredirectUris: string[];\n\tscopes: string[] | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// Handlers\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new OAuth client.\n */\nexport async function handleOAuthClientCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tid: string;\n\t\tname: string;\n\t\tredirectUris: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tif (input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\tif (redirectUriError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Check for duplicate client ID\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"id\", \"=\", input.id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"CONFLICT\", message: \"OAuth client with this ID already exists\" },\n\t\t\t};\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\n\t\tawait db\n\t\t\t.insertInto(\"_emdash_oauth_clients\")\n\t\t\t.values({\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirect_uris: JSON.stringify(input.redirectUris),\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null,\n\t\t\t})\n\t\t\t.execute();\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: input.id,\n\t\t\t\tname: input.name,\n\t\t\t\tredirectUris: input.redirectUris,\n\t\t\t\tscopes: input.scopes && input.scopes.length > 0 ? input.scopes : null,\n\t\t\t\tcreatedAt: now,\n\t\t\t\tupdatedAt: now,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_CREATE_ERROR\",\n\t\t\t\tmessage: \"Failed to create OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * List all registered OAuth clients.\n */\nexport async function handleOAuthClientList(\n\tdb: Kysely<Database>,\n): Promise<ApiResult<{ items: OAuthClientInfo[] }>> {\n\ttry {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.execute();\n\n\t\tconst items: OAuthClientInfo[] = rows.map((row) => ({\n\t\t\tid: row.id,\n\t\t\tname: row.name,\n\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tupdatedAt: row.updated_at,\n\t\t}));\n\n\t\treturn { success: true, data: { items } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list OAuth clients\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get a single OAuth client by ID.\n */\nexport async function handleOAuthClientGet(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst row = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\t\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t\t\t\tcreatedAt: row.created_at,\n\t\t\t\tupdatedAt: row.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Update an OAuth client.\n */\nexport async function handleOAuthClientUpdate(\n\tdb: Kysely<Database>,\n\tclientId: string,\n\tinput: {\n\t\tname?: string;\n\t\tredirectUris?: string[];\n\t\tscopes?: string[] | null;\n\t},\n): Promise<ApiResult<OAuthClientInfo>> {\n\ttry {\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!existing) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined && input.redirectUris.length === 0) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: \"At least one redirect URI is required\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tconst redirectUriError = validateRegisteredRedirectUris(input.redirectUris);\n\t\t\tif (redirectUriError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\tmessage: redirectUriError,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst updates: Record<string, string | null> = {\n\t\t\tupdated_at: new Date().toISOString(),\n\t\t};\n\n\t\tif (input.name !== undefined) {\n\t\t\tupdates.name = input.name;\n\t\t}\n\t\tif (input.redirectUris !== undefined) {\n\t\t\tupdates.redirect_uris = JSON.stringify(input.redirectUris);\n\t\t}\n\t\tif (input.scopes !== undefined) {\n\t\t\tupdates.scopes =\n\t\t\t\tinput.scopes && input.scopes.length > 0 ? JSON.stringify(input.scopes) : null;\n\t\t}\n\n\t\tawait db.updateTable(\"_emdash_oauth_clients\").set(updates).where(\"id\", \"=\", clientId).execute();\n\n\t\t// Fetch the updated row\n\t\tconst updated = await db\n\t\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!updated) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found after update\" },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tid: updated.id,\n\t\t\t\tname: updated.name,\n\t\t\t\tredirectUris: parseJsonColumn<string[]>(updated.redirect_uris),\n\t\t\t\tscopes: updated.scopes ? parseJsonColumn<string[]>(updated.scopes) : null,\n\t\t\t\tcreatedAt: updated.created_at,\n\t\t\t\tupdatedAt: updated.updated_at,\n\t\t\t},\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_UPDATE_ERROR\",\n\t\t\t\tmessage: \"Failed to update OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Delete an OAuth client.\n */\nexport async function handleOAuthClientDelete(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst result = await db\n\t\t\t.deleteFrom(\"_emdash_oauth_clients\")\n\t\t\t.where(\"id\", \"=\", clientId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (result.numDeletedRows === 0n) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: \"OAuth client not found\" },\n\t\t\t};\n\t\t}\n\n\t\treturn { success: true, data: { deleted: true } };\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CLIENT_DELETE_ERROR\",\n\t\t\t\tmessage: \"Failed to delete OAuth client\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Lookup helpers (used by authorization handler)\n// ---------------------------------------------------------------------------\n\n/**\n * Look up a registered OAuth client by ID.\n * Returns the client's redirect URIs or null if the client is not registered.\n */\nexport async function lookupOAuthClient(\n\tdb: Kysely<Database>,\n\tclientId: string,\n): Promise<{ redirectUris: string[]; scopes: string[] | null } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_oauth_clients\")\n\t\t.select([\"redirect_uris\", \"scopes\"])\n\t\t.where(\"id\", \"=\", clientId)\n\t\t.executeTakeFirst();\n\n\tif (!row) return null;\n\n\treturn {\n\t\tredirectUris: parseJsonColumn<string[]>(row.redirect_uris),\n\t\tscopes: row.scopes ? parseJsonColumn<string[]>(row.scopes) : null,\n\t};\n}\n\n/**\n * Validate that a redirect URI is in the client's registered set.\n *\n * Comparison is exact string match (per RFC 6749 §3.1.2.3).\n * Returns null if valid, or an error message if not.\n */\nexport function validateClientRedirectUri(\n\tredirectUri: string,\n\tallowedUris: string[],\n): string | null {\n\tif (allowedUris.includes(redirectUri)) {\n\t\treturn null; // OK\n\t}\n\treturn \"redirect_uri is not registered for this client\";\n}\n"],"mappings":"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"}
@@ -47,4 +47,4 @@ function createOAuthStateStore(db) {
47
47
 
48
48
  //#endregion
49
49
  export { createOAuthStateStore as t };
50
- //# sourceMappingURL=oauth-state-store-DwAxMtk4.mjs.map
50
+ //# sourceMappingURL=oauth-state-store-0C4s0K6w.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-state-store-DwAxMtk4.mjs","names":[],"sources":["../src/auth/oauth-state-store.ts"],"sourcesContent":["/**\n * OAuth state store\n *\n * Stores OAuth state in the auth_challenges table with automatic expiration.\n * Uses the existing table but with type=\"oauth\" to distinguish from WebAuthn challenges.\n */\n\nimport type { StateStore, OAuthState } from \"@emdash-cms/auth\";\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\n\nconst OAUTH_STATE_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nexport function createOAuthStateStore(db: Kysely<Database>): StateStore {\n\treturn {\n\t\tasync set(state: string, data: OAuthState): Promise<void> {\n\t\t\tconst expiresAt = new Date(Date.now() + OAUTH_STATE_TTL_MS).toISOString();\n\n\t\t\tawait db\n\t\t\t\t.insertInto(\"auth_challenges\")\n\t\t\t\t.values({\n\t\t\t\t\tchallenge: state,\n\t\t\t\t\ttype: \"oauth\",\n\t\t\t\t\tuser_id: null,\n\t\t\t\t\tdata: JSON.stringify(data),\n\t\t\t\t\texpires_at: expiresAt,\n\t\t\t\t})\n\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\toc.column(\"challenge\").doUpdateSet({\n\t\t\t\t\t\ttype: \"oauth\",\n\t\t\t\t\t\tdata: JSON.stringify(data),\n\t\t\t\t\t\texpires_at: expiresAt,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.execute();\n\t\t},\n\n\t\tasync get(state: string): Promise<OAuthState | null> {\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"auth_challenges\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"challenge\", \"=\", state)\n\t\t\t\t.where(\"type\", \"=\", \"oauth\")\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!row) return null;\n\n\t\t\tconst expiresAt = new Date(row.expires_at).getTime();\n\n\t\t\t// Check expiration\n\t\t\tif (expiresAt < Date.now()) {\n\t\t\t\t// Expired, delete and return null\n\t\t\t\tawait this.delete(state);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (!row.data) return null;\n\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(row.data);\n\t\t\t\tif (\n\t\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\t\tparsed === null ||\n\t\t\t\t\t!(\"provider\" in parsed) ||\n\t\t\t\t\ttypeof parsed.provider !== \"string\" ||\n\t\t\t\t\t!(\"redirectUri\" in parsed) ||\n\t\t\t\t\ttypeof parsed.redirectUri !== \"string\"\n\t\t\t\t) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst oauthState: OAuthState = {\n\t\t\t\t\tprovider: parsed.provider,\n\t\t\t\t\tredirectUri: parsed.redirectUri,\n\t\t\t\t};\n\t\t\t\tif (\"codeVerifier\" in parsed && typeof parsed.codeVerifier === \"string\") {\n\t\t\t\t\toauthState.codeVerifier = parsed.codeVerifier;\n\t\t\t\t}\n\t\t\t\tif (\"nonce\" in parsed && typeof parsed.nonce === \"string\") {\n\t\t\t\t\toauthState.nonce = parsed.nonce;\n\t\t\t\t}\n\t\t\t\tif (\"inviteToken\" in parsed && typeof parsed.inviteToken === \"string\") {\n\t\t\t\t\toauthState.inviteToken = parsed.inviteToken;\n\t\t\t\t}\n\t\t\t\treturn oauthState;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync delete(state: string): Promise<void> {\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"auth_challenges\")\n\t\t\t\t.where(\"challenge\", \"=\", state)\n\t\t\t\t.where(\"type\", \"=\", \"oauth\")\n\t\t\t\t.execute();\n\t\t},\n\t};\n}\n"],"mappings":";AAYA,MAAM,qBAAqB,MAAU;AAErC,SAAgB,sBAAsB,IAAkC;AACvE,QAAO;EACN,MAAM,IAAI,OAAe,MAAiC;GACzD,MAAM,YAAY,IAAI,KAAK,KAAK,KAAK,GAAG,mBAAmB,CAAC,aAAa;AAEzE,SAAM,GACJ,WAAW,kBAAkB,CAC7B,OAAO;IACP,WAAW;IACX,MAAM;IACN,SAAS;IACT,MAAM,KAAK,UAAU,KAAK;IAC1B,YAAY;IACZ,CAAC,CACD,YAAY,OACZ,GAAG,OAAO,YAAY,CAAC,YAAY;IAClC,MAAM;IACN,MAAM,KAAK,UAAU,KAAK;IAC1B,YAAY;IACZ,CAAC,CACF,CACA,SAAS;;EAGZ,MAAM,IAAI,OAA2C;GACpD,MAAM,MAAM,MAAM,GAChB,WAAW,kBAAkB,CAC7B,WAAW,CACX,MAAM,aAAa,KAAK,MAAM,CAC9B,MAAM,QAAQ,KAAK,QAAQ,CAC3B,kBAAkB;AAEpB,OAAI,CAAC,IAAK,QAAO;AAKjB,OAHkB,IAAI,KAAK,IAAI,WAAW,CAAC,SAAS,GAGpC,KAAK,KAAK,EAAE;AAE3B,UAAM,KAAK,OAAO,MAAM;AACxB,WAAO;;AAGR,OAAI,CAAC,IAAI,KAAM,QAAO;AAEtB,OAAI;IACH,MAAM,SAAkB,KAAK,MAAM,IAAI,KAAK;AAC5C,QACC,OAAO,WAAW,YAClB,WAAW,QACX,EAAE,cAAc,WAChB,OAAO,OAAO,aAAa,YAC3B,EAAE,iBAAiB,WACnB,OAAO,OAAO,gBAAgB,SAE9B,QAAO;IAER,MAAM,aAAyB;KAC9B,UAAU,OAAO;KACjB,aAAa,OAAO;KACpB;AACD,QAAI,kBAAkB,UAAU,OAAO,OAAO,iBAAiB,SAC9D,YAAW,eAAe,OAAO;AAElC,QAAI,WAAW,UAAU,OAAO,OAAO,UAAU,SAChD,YAAW,QAAQ,OAAO;AAE3B,QAAI,iBAAiB,UAAU,OAAO,OAAO,gBAAgB,SAC5D,YAAW,cAAc,OAAO;AAEjC,WAAO;WACA;AACP,WAAO;;;EAIT,MAAM,OAAO,OAA8B;AAC1C,SAAM,GACJ,WAAW,kBAAkB,CAC7B,MAAM,aAAa,KAAK,MAAM,CAC9B,MAAM,QAAQ,KAAK,QAAQ,CAC3B,SAAS;;EAEZ"}
1
+ {"version":3,"file":"oauth-state-store-0C4s0K6w.mjs","names":[],"sources":["../src/auth/oauth-state-store.ts"],"sourcesContent":["/**\n * OAuth state store\n *\n * Stores OAuth state in the auth_challenges table with automatic expiration.\n * Uses the existing table but with type=\"oauth\" to distinguish from WebAuthn challenges.\n */\n\nimport type { StateStore, OAuthState } from \"@emdash-cms/auth\";\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\n\nconst OAUTH_STATE_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nexport function createOAuthStateStore(db: Kysely<Database>): StateStore {\n\treturn {\n\t\tasync set(state: string, data: OAuthState): Promise<void> {\n\t\t\tconst expiresAt = new Date(Date.now() + OAUTH_STATE_TTL_MS).toISOString();\n\n\t\t\tawait db\n\t\t\t\t.insertInto(\"auth_challenges\")\n\t\t\t\t.values({\n\t\t\t\t\tchallenge: state,\n\t\t\t\t\ttype: \"oauth\",\n\t\t\t\t\tuser_id: null,\n\t\t\t\t\tdata: JSON.stringify(data),\n\t\t\t\t\texpires_at: expiresAt,\n\t\t\t\t})\n\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\toc.column(\"challenge\").doUpdateSet({\n\t\t\t\t\t\ttype: \"oauth\",\n\t\t\t\t\t\tdata: JSON.stringify(data),\n\t\t\t\t\t\texpires_at: expiresAt,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.execute();\n\t\t},\n\n\t\tasync get(state: string): Promise<OAuthState | null> {\n\t\t\tconst row = await db\n\t\t\t\t.selectFrom(\"auth_challenges\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"challenge\", \"=\", state)\n\t\t\t\t.where(\"type\", \"=\", \"oauth\")\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!row) return null;\n\n\t\t\tconst expiresAt = new Date(row.expires_at).getTime();\n\n\t\t\t// Check expiration\n\t\t\tif (expiresAt < Date.now()) {\n\t\t\t\t// Expired, delete and return null\n\t\t\t\tawait this.delete(state);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (!row.data) return null;\n\n\t\t\ttry {\n\t\t\t\tconst parsed: unknown = JSON.parse(row.data);\n\t\t\t\tif (\n\t\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\t\tparsed === null ||\n\t\t\t\t\t!(\"provider\" in parsed) ||\n\t\t\t\t\ttypeof parsed.provider !== \"string\" ||\n\t\t\t\t\t!(\"redirectUri\" in parsed) ||\n\t\t\t\t\ttypeof parsed.redirectUri !== \"string\"\n\t\t\t\t) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst oauthState: OAuthState = {\n\t\t\t\t\tprovider: parsed.provider,\n\t\t\t\t\tredirectUri: parsed.redirectUri,\n\t\t\t\t};\n\t\t\t\tif (\"codeVerifier\" in parsed && typeof parsed.codeVerifier === \"string\") {\n\t\t\t\t\toauthState.codeVerifier = parsed.codeVerifier;\n\t\t\t\t}\n\t\t\t\tif (\"nonce\" in parsed && typeof parsed.nonce === \"string\") {\n\t\t\t\t\toauthState.nonce = parsed.nonce;\n\t\t\t\t}\n\t\t\t\tif (\"inviteToken\" in parsed && typeof parsed.inviteToken === \"string\") {\n\t\t\t\t\toauthState.inviteToken = parsed.inviteToken;\n\t\t\t\t}\n\t\t\t\treturn oauthState;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\n\t\tasync delete(state: string): Promise<void> {\n\t\t\tawait db\n\t\t\t\t.deleteFrom(\"auth_challenges\")\n\t\t\t\t.where(\"challenge\", \"=\", state)\n\t\t\t\t.where(\"type\", \"=\", \"oauth\")\n\t\t\t\t.execute();\n\t\t},\n\t};\n}\n"],"mappings":";AAYA,MAAM,qBAAqB,MAAU;AAErC,SAAgB,sBAAsB,IAAkC;AACvE,QAAO;EACN,MAAM,IAAI,OAAe,MAAiC;GACzD,MAAM,YAAY,IAAI,KAAK,KAAK,KAAK,GAAG,mBAAmB,CAAC,aAAa;AAEzE,SAAM,GACJ,WAAW,kBAAkB,CAC7B,OAAO;IACP,WAAW;IACX,MAAM;IACN,SAAS;IACT,MAAM,KAAK,UAAU,KAAK;IAC1B,YAAY;IACZ,CAAC,CACD,YAAY,OACZ,GAAG,OAAO,YAAY,CAAC,YAAY;IAClC,MAAM;IACN,MAAM,KAAK,UAAU,KAAK;IAC1B,YAAY;IACZ,CAAC,CACF,CACA,SAAS;;EAGZ,MAAM,IAAI,OAA2C;GACpD,MAAM,MAAM,MAAM,GAChB,WAAW,kBAAkB,CAC7B,WAAW,CACX,MAAM,aAAa,KAAK,MAAM,CAC9B,MAAM,QAAQ,KAAK,QAAQ,CAC3B,kBAAkB;AAEpB,OAAI,CAAC,IAAK,QAAO;AAKjB,OAHkB,IAAI,KAAK,IAAI,WAAW,CAAC,SAAS,GAGpC,KAAK,KAAK,EAAE;AAE3B,UAAM,KAAK,OAAO,MAAM;AACxB,WAAO;;AAGR,OAAI,CAAC,IAAI,KAAM,QAAO;AAEtB,OAAI;IACH,MAAM,SAAkB,KAAK,MAAM,IAAI,KAAK;AAC5C,QACC,OAAO,WAAW,YAClB,WAAW,QACX,EAAE,cAAc,WAChB,OAAO,OAAO,aAAa,YAC3B,EAAE,iBAAiB,WACnB,OAAO,OAAO,gBAAgB,SAE9B,QAAO;IAER,MAAM,aAAyB;KAC9B,UAAU,OAAO;KACjB,aAAa,OAAO;KACpB;AACD,QAAI,kBAAkB,UAAU,OAAO,OAAO,iBAAiB,SAC9D,YAAW,eAAe,OAAO;AAElC,QAAI,WAAW,UAAU,OAAO,OAAO,UAAU,SAChD,YAAW,QAAQ,OAAO;AAE3B,QAAI,iBAAiB,UAAU,OAAO,OAAO,gBAAgB,SAC5D,YAAW,cAAc,OAAO;AAEjC,WAAO;WACA;AACP,WAAO;;;EAIT,MAAM,OAAO,OAA8B;AAC1C,SAAM,GACJ,WAAW,kBAAkB,CAC7B,MAAM,aAAa,KAAK,MAAM,CAC9B,MAAM,QAAQ,KAAK,QAAQ,CAC3B,SAAS;;EAEZ"}
@@ -23,4 +23,4 @@ async function lookupUserRoleAndStatus(db, userId) {
23
23
 
24
24
  //#endregion
25
25
  export { lookupUserRoleAndStatus as t };
26
- //# sourceMappingURL=oauth-user-lookup-BxfZizdk.mjs.map
26
+ //# sourceMappingURL=oauth-user-lookup-wlGYSeqX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-user-lookup-BxfZizdk.mjs","names":[],"sources":["../src/api/handlers/oauth-user-lookup.ts"],"sourcesContent":["/**\n * Shared user lookup for OAuth token operations.\n *\n * Extracts user role and disabled status from the database. Used by\n * handleTokenRefresh() to revalidate scopes against the user's current\n * role and reject disabled users.\n */\n\nimport { toRoleLevel, type RoleLevel } from \"@emdash-cms/auth\";\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\n\nexport interface UserRoleAndStatus {\n\trole: RoleLevel;\n\tdisabled: boolean;\n}\n\n/**\n * Look up a user's current role and disabled status.\n * Returns null if the user doesn't exist.\n */\nexport async function lookupUserRoleAndStatus(\n\tdb: Kysely<Database>,\n\tuserId: string,\n): Promise<UserRoleAndStatus | null> {\n\tconst row = await db\n\t\t.selectFrom(\"users\")\n\t\t.select([\"role\", \"disabled\"])\n\t\t.where(\"id\", \"=\", userId)\n\t\t.executeTakeFirst();\n\n\tif (!row) return null;\n\n\treturn {\n\t\trole: toRoleLevel(row.role),\n\t\tdisabled: row.disabled === 1,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,eAAsB,wBACrB,IACA,QACoC;CACpC,MAAM,MAAM,MAAM,GAChB,WAAW,QAAQ,CACnB,OAAO,CAAC,QAAQ,WAAW,CAAC,CAC5B,MAAM,MAAM,KAAK,OAAO,CACxB,kBAAkB;AAEpB,KAAI,CAAC,IAAK,QAAO;AAEjB,QAAO;EACN,MAAM,YAAY,IAAI,KAAK;EAC3B,UAAU,IAAI,aAAa;EAC3B"}
1
+ {"version":3,"file":"oauth-user-lookup-wlGYSeqX.mjs","names":[],"sources":["../src/api/handlers/oauth-user-lookup.ts"],"sourcesContent":["/**\n * Shared user lookup for OAuth token operations.\n *\n * Extracts user role and disabled status from the database. Used by\n * handleTokenRefresh() to revalidate scopes against the user's current\n * role and reject disabled users.\n */\n\nimport { toRoleLevel, type RoleLevel } from \"@emdash-cms/auth\";\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\n\nexport interface UserRoleAndStatus {\n\trole: RoleLevel;\n\tdisabled: boolean;\n}\n\n/**\n * Look up a user's current role and disabled status.\n * Returns null if the user doesn't exist.\n */\nexport async function lookupUserRoleAndStatus(\n\tdb: Kysely<Database>,\n\tuserId: string,\n): Promise<UserRoleAndStatus | null> {\n\tconst row = await db\n\t\t.selectFrom(\"users\")\n\t\t.select([\"role\", \"disabled\"])\n\t\t.where(\"id\", \"=\", userId)\n\t\t.executeTakeFirst();\n\n\tif (!row) return null;\n\n\treturn {\n\t\trole: toRoleLevel(row.role),\n\t\tdisabled: row.disabled === 1,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,eAAsB,wBACrB,IACA,QACoC;CACpC,MAAM,MAAM,MAAM,GAChB,WAAW,QAAQ,CACnB,OAAO,CAAC,QAAQ,WAAW,CAAC,CAC5B,MAAM,MAAM,KAAK,OAAO,CACxB,kBAAkB;AAEpB,KAAI,CAAC,IAAK,QAAO;AAEjB,QAAO;EACN,MAAM,YAAY,IAAI,KAAK;EAC3B,UAAU,IAAI,aAAa;EAC3B"}
@@ -1,4 +1,4 @@
1
- import { t as CreateObjectCacheBackendFn } from "../types-X0MThr7x.mjs";
1
+ import { t as CreateObjectCacheBackendFn } from "../types-D5VVLr7J.mjs";
2
2
 
3
3
  //#region src/object-cache/memory.d.ts
4
4
  /**
@@ -114,4 +114,4 @@ var OptionsRepository = class {
114
114
 
115
115
  //#endregion
116
116
  export { OptionsRepository as t };
117
- //# sourceMappingURL=options-CBXIHUoT.mjs.map
117
+ //# sourceMappingURL=options-BHPeIbBd.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"options-CBXIHUoT.mjs","names":[],"sources":["../src/database/repositories/options.ts"],"sourcesContent":["import { sql, type Kysely, type SqlBool } from \"kysely\";\n\nimport type { Database, OptionTable } from \"../types.js\";\n\nfunction escapeLike(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n}\n\n/**\n * Options repository for key-value settings storage\n *\n * Used for site settings, plugin configuration, and other arbitrary key-value data.\n * Values are stored as JSON for flexibility.\n */\nexport class OptionsRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Get an option value\n\t */\n\tasync get<T = unknown>(name: string): Promise<T | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select(\"value\")\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) return null;\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\treturn JSON.parse(row.value) as T;\n\t}\n\n\t/**\n\t * Get an option value with a default\n\t */\n\tasync getOrDefault<T>(name: string, defaultValue: T): Promise<T> {\n\t\tconst value = await this.get<T>(name);\n\t\treturn value ?? defaultValue;\n\t}\n\n\t/**\n\t * Set an option value (creates or updates)\n\t */\n\tasync set<T = unknown>(name: string, value: T): Promise<void> {\n\t\tconst row: OptionTable = {\n\t\t\tname,\n\t\t\tvalue: JSON.stringify(value),\n\t\t};\n\n\t\t// Upsert: insert or replace\n\t\tawait this.db\n\t\t\t.insertInto(\"options\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) => oc.column(\"name\").doUpdateSet({ value: row.value }))\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Set an option value only if no row with that name exists. Atomic at the\n\t * database level via INSERT ... ON CONFLICT DO NOTHING, so concurrent\n\t * callers can't race past the check.\n\t *\n\t * Returns true when the row was inserted, false when a row already\n\t * existed (regardless of its value — even an empty string or null).\n\t */\n\tasync setIfAbsent<T = unknown>(name: string, value: T): Promise<boolean> {\n\t\tconst row: OptionTable = {\n\t\t\tname,\n\t\t\tvalue: JSON.stringify(value),\n\t\t};\n\n\t\tconst result = await this.db\n\t\t\t.insertInto(\"options\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) => oc.column(\"name\").doNothing())\n\t\t\t.executeTakeFirst();\n\n\t\t// SQLite reports numInsertedOrUpdatedRows; Postgres reports the same.\n\t\t// When the ON CONFLICT branch fires and does nothing, the count is 0.\n\t\treturn (result.numInsertedOrUpdatedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Delete an option\n\t */\n\tasync delete(name: string): Promise<boolean> {\n\t\tconst result = await this.db.deleteFrom(\"options\").where(\"name\", \"=\", name).executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Check if an option exists\n\t */\n\tasync exists(name: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select(\"name\")\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.executeTakeFirst();\n\n\t\treturn !!row;\n\t}\n\n\t/**\n\t * Get multiple options at once\n\t */\n\tasync getMany<T = unknown>(names: string[]): Promise<Map<string, T>> {\n\t\tif (names.length === 0) return new Map();\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select([\"name\", \"value\"])\n\t\t\t.where(\"name\", \"in\", names)\n\t\t\t.execute();\n\n\t\tconst result = new Map<string, T>();\n\t\tfor (const row of rows) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tresult.set(row.name, JSON.parse(row.value) as T);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Set multiple options at once\n\t */\n\tasync setMany<T = unknown>(options: Record<string, T>): Promise<void> {\n\t\tconst entries = Object.entries(options);\n\t\tif (entries.length === 0) return;\n\n\t\tfor (const [name, value] of entries) {\n\t\t\tawait this.set(name, value);\n\t\t}\n\t}\n\n\t/**\n\t * Get all options (use sparingly)\n\t */\n\tasync getAll(): Promise<Map<string, unknown>> {\n\t\tconst rows = await this.db.selectFrom(\"options\").select([\"name\", \"value\"]).execute();\n\n\t\tconst result = new Map<string, unknown>();\n\t\tfor (const row of rows) {\n\t\t\tresult.set(row.name, JSON.parse(row.value));\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get all options matching a prefix\n\t */\n\tasync getByPrefix<T = unknown>(prefix: string): Promise<Map<string, T>> {\n\t\tconst pattern = `${escapeLike(prefix)}%`;\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select([\"name\", \"value\"])\n\t\t\t.where(sql<SqlBool>`name LIKE ${pattern} ESCAPE '\\\\'`)\n\t\t\t.execute();\n\n\t\tconst result = new Map<string, T>();\n\t\tfor (const row of rows) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tresult.set(row.name, JSON.parse(row.value) as T);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Delete all options matching a prefix\n\t */\n\tasync deleteByPrefix(prefix: string): Promise<number> {\n\t\tconst pattern = `${escapeLike(prefix)}%`;\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"options\")\n\t\t\t.where(sql<SqlBool>`name LIKE ${pattern} ESCAPE '\\\\'`)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"options-BHPeIbBd.mjs","names":[],"sources":["../src/database/repositories/options.ts"],"sourcesContent":["import { sql, type Kysely, type SqlBool } from \"kysely\";\n\nimport type { Database, OptionTable } from \"../types.js\";\n\nfunction escapeLike(value: string): string {\n\treturn value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n}\n\n/**\n * Options repository for key-value settings storage\n *\n * Used for site settings, plugin configuration, and other arbitrary key-value data.\n * Values are stored as JSON for flexibility.\n */\nexport class OptionsRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Get an option value\n\t */\n\tasync get<T = unknown>(name: string): Promise<T | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select(\"value\")\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) return null;\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\treturn JSON.parse(row.value) as T;\n\t}\n\n\t/**\n\t * Get an option value with a default\n\t */\n\tasync getOrDefault<T>(name: string, defaultValue: T): Promise<T> {\n\t\tconst value = await this.get<T>(name);\n\t\treturn value ?? defaultValue;\n\t}\n\n\t/**\n\t * Set an option value (creates or updates)\n\t */\n\tasync set<T = unknown>(name: string, value: T): Promise<void> {\n\t\tconst row: OptionTable = {\n\t\t\tname,\n\t\t\tvalue: JSON.stringify(value),\n\t\t};\n\n\t\t// Upsert: insert or replace\n\t\tawait this.db\n\t\t\t.insertInto(\"options\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) => oc.column(\"name\").doUpdateSet({ value: row.value }))\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Set an option value only if no row with that name exists. Atomic at the\n\t * database level via INSERT ... ON CONFLICT DO NOTHING, so concurrent\n\t * callers can't race past the check.\n\t *\n\t * Returns true when the row was inserted, false when a row already\n\t * existed (regardless of its value — even an empty string or null).\n\t */\n\tasync setIfAbsent<T = unknown>(name: string, value: T): Promise<boolean> {\n\t\tconst row: OptionTable = {\n\t\t\tname,\n\t\t\tvalue: JSON.stringify(value),\n\t\t};\n\n\t\tconst result = await this.db\n\t\t\t.insertInto(\"options\")\n\t\t\t.values(row)\n\t\t\t.onConflict((oc) => oc.column(\"name\").doNothing())\n\t\t\t.executeTakeFirst();\n\n\t\t// SQLite reports numInsertedOrUpdatedRows; Postgres reports the same.\n\t\t// When the ON CONFLICT branch fires and does nothing, the count is 0.\n\t\treturn (result.numInsertedOrUpdatedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Delete an option\n\t */\n\tasync delete(name: string): Promise<boolean> {\n\t\tconst result = await this.db.deleteFrom(\"options\").where(\"name\", \"=\", name).executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Check if an option exists\n\t */\n\tasync exists(name: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select(\"name\")\n\t\t\t.where(\"name\", \"=\", name)\n\t\t\t.executeTakeFirst();\n\n\t\treturn !!row;\n\t}\n\n\t/**\n\t * Get multiple options at once\n\t */\n\tasync getMany<T = unknown>(names: string[]): Promise<Map<string, T>> {\n\t\tif (names.length === 0) return new Map();\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select([\"name\", \"value\"])\n\t\t\t.where(\"name\", \"in\", names)\n\t\t\t.execute();\n\n\t\tconst result = new Map<string, T>();\n\t\tfor (const row of rows) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tresult.set(row.name, JSON.parse(row.value) as T);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Set multiple options at once\n\t */\n\tasync setMany<T = unknown>(options: Record<string, T>): Promise<void> {\n\t\tconst entries = Object.entries(options);\n\t\tif (entries.length === 0) return;\n\n\t\tfor (const [name, value] of entries) {\n\t\t\tawait this.set(name, value);\n\t\t}\n\t}\n\n\t/**\n\t * Get all options (use sparingly)\n\t */\n\tasync getAll(): Promise<Map<string, unknown>> {\n\t\tconst rows = await this.db.selectFrom(\"options\").select([\"name\", \"value\"]).execute();\n\n\t\tconst result = new Map<string, unknown>();\n\t\tfor (const row of rows) {\n\t\t\tresult.set(row.name, JSON.parse(row.value));\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get all options matching a prefix\n\t */\n\tasync getByPrefix<T = unknown>(prefix: string): Promise<Map<string, T>> {\n\t\tconst pattern = `${escapeLike(prefix)}%`;\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"options\")\n\t\t\t.select([\"name\", \"value\"])\n\t\t\t.where(sql<SqlBool>`name LIKE ${pattern} ESCAPE '\\\\'`)\n\t\t\t.execute();\n\n\t\tconst result = new Map<string, T>();\n\t\tfor (const row of rows) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- JSON.parse returns any; generic callers provide T\n\t\t\tresult.set(row.name, JSON.parse(row.value) as T);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Delete all options matching a prefix\n\t */\n\tasync deleteByPrefix(prefix: string): Promise<number> {\n\t\tconst pattern = `${escapeLike(prefix)}%`;\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"options\")\n\t\t\t.where(sql<SqlBool>`name LIKE ${pattern} ESCAPE '\\\\'`)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n}\n"],"mappings":";;;AAIA,SAAS,WAAW,OAAuB;AAC1C,QAAO,MAAM,WAAW,MAAM,OAAO,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,KAAK,MAAM;;;;;;;;AASpF,IAAa,oBAAb,MAA+B;CAC9B,YAAY,AAAQ,IAAsB;EAAtB;;;;;CAKpB,MAAM,IAAiB,MAAiC;EACvD,MAAM,MAAM,MAAM,KAAK,GACrB,WAAW,UAAU,CACrB,OAAO,QAAQ,CACf,MAAM,QAAQ,KAAK,KAAK,CACxB,kBAAkB;AAEpB,MAAI,CAAC,IAAK,QAAO;AAEjB,SAAO,KAAK,MAAM,IAAI,MAAM;;;;;CAM7B,MAAM,aAAgB,MAAc,cAA6B;AAEhE,SADc,MAAM,KAAK,IAAO,KAAK,IACrB;;;;;CAMjB,MAAM,IAAiB,MAAc,OAAyB;EAC7D,MAAM,MAAmB;GACxB;GACA,OAAO,KAAK,UAAU,MAAM;GAC5B;AAGD,QAAM,KAAK,GACT,WAAW,UAAU,CACrB,OAAO,IAAI,CACX,YAAY,OAAO,GAAG,OAAO,OAAO,CAAC,YAAY,EAAE,OAAO,IAAI,OAAO,CAAC,CAAC,CACvE,SAAS;;;;;;;;;;CAWZ,MAAM,YAAyB,MAAc,OAA4B;EACxE,MAAM,MAAmB;GACxB;GACA,OAAO,KAAK,UAAU,MAAM;GAC5B;AAUD,WARe,MAAM,KAAK,GACxB,WAAW,UAAU,CACrB,OAAO,IAAI,CACX,YAAY,OAAO,GAAG,OAAO,OAAO,CAAC,WAAW,CAAC,CACjD,kBAAkB,EAIL,4BAA4B,MAAM;;;;;CAMlD,MAAM,OAAO,MAAgC;AAG5C,WAFe,MAAM,KAAK,GAAG,WAAW,UAAU,CAAC,MAAM,QAAQ,KAAK,KAAK,CAAC,kBAAkB,EAE/E,kBAAkB,KAAK;;;;;CAMvC,MAAM,OAAO,MAAgC;AAO5C,SAAO,CAAC,CANI,MAAM,KAAK,GACrB,WAAW,UAAU,CACrB,OAAO,OAAO,CACd,MAAM,QAAQ,KAAK,KAAK,CACxB,kBAAkB;;;;;CAQrB,MAAM,QAAqB,OAA0C;AACpE,MAAI,MAAM,WAAW,EAAG,wBAAO,IAAI,KAAK;EAExC,MAAM,OAAO,MAAM,KAAK,GACtB,WAAW,UAAU,CACrB,OAAO,CAAC,QAAQ,QAAQ,CAAC,CACzB,MAAM,QAAQ,MAAM,MAAM,CAC1B,SAAS;EAEX,MAAM,yBAAS,IAAI,KAAgB;AACnC,OAAK,MAAM,OAAO,KAEjB,QAAO,IAAI,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,CAAM;AAEjD,SAAO;;;;;CAMR,MAAM,QAAqB,SAA2C;EACrE,MAAM,UAAU,OAAO,QAAQ,QAAQ;AACvC,MAAI,QAAQ,WAAW,EAAG;AAE1B,OAAK,MAAM,CAAC,MAAM,UAAU,QAC3B,OAAM,KAAK,IAAI,MAAM,MAAM;;;;;CAO7B,MAAM,SAAwC;EAC7C,MAAM,OAAO,MAAM,KAAK,GAAG,WAAW,UAAU,CAAC,OAAO,CAAC,QAAQ,QAAQ,CAAC,CAAC,SAAS;EAEpF,MAAM,yBAAS,IAAI,KAAsB;AACzC,OAAK,MAAM,OAAO,KACjB,QAAO,IAAI,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC;AAE5C,SAAO;;;;;CAMR,MAAM,YAAyB,QAAyC;EACvE,MAAM,UAAU,GAAG,WAAW,OAAO,CAAC;EACtC,MAAM,OAAO,MAAM,KAAK,GACtB,WAAW,UAAU,CACrB,OAAO,CAAC,QAAQ,QAAQ,CAAC,CACzB,MAAM,GAAY,aAAa,QAAQ,cAAc,CACrD,SAAS;EAEX,MAAM,yBAAS,IAAI,KAAgB;AACnC,OAAK,MAAM,OAAO,KAEjB,QAAO,IAAI,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,CAAM;AAEjD,SAAO;;;;;CAMR,MAAM,eAAe,QAAiC;EACrD,MAAM,UAAU,GAAG,WAAW,OAAO,CAAC;EACtC,MAAM,SAAS,MAAM,KAAK,GACxB,WAAW,UAAU,CACrB,MAAM,GAAY,aAAa,QAAQ,cAAc,CACrD,kBAAkB;AAEpB,SAAO,OAAO,OAAO,kBAAkB,EAAE"}
@@ -1,5 +1,5 @@
1
- import { i as ContentItem } from "./types-D4_1Ga4L.mjs";
2
- import { t as Database } from "./types-CAGdePf3.mjs";
1
+ import { i as ContentItem } from "./types-DJQxln7q.mjs";
2
+ import { t as Database } from "./types-DHxzBjbD.mjs";
3
3
  import { Kysely } from "kysely";
4
4
  import { z } from "zod";
5
5
 
@@ -205,4 +205,4 @@ declare class OptionsRepository {
205
205
  }
206
206
  //#endregion
207
207
  export { parseQuery as a, handleError as c, ContentListResponse as d, ContentResponse as f, ManifestResponse as h, parseBody as i, ApiContext as l, ListResponse as m, ParseResult as n, apiError as o, FieldDescriptor as p, isParseError as r, apiSuccess as s, OptionsRepository as t, ApiResult as u };
208
- //# sourceMappingURL=options-DRe1pIBL.d.mts.map
208
+ //# sourceMappingURL=options-BhZIeFY1.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"options-DRe1pIBL.d.mts","names":[],"sources":["../src/api/types.ts","../src/api/error.ts","../src/api/parse.ts","../src/database/repositories/options.ts"],"mappings":"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"}
1
+ {"version":3,"file":"options-BhZIeFY1.d.mts","names":[],"sources":["../src/api/types.ts","../src/api/error.ts","../src/api/parse.ts","../src/database/repositories/options.ts"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { Ct as PublicPageContext, ct as PagePlacement, it as PageMetadataContribution, st as PageMetadataLinkRel, t as BreadcrumbItem, tt as PageFragmentContribution } from "../types-Cs-TFLNf.mjs";
2
- import { n as SeoSettings } from "../types-CPx2lIGo.mjs";
1
+ import { Ct as PublicPageContext, ct as PagePlacement, it as PageMetadataContribution, st as PageMetadataLinkRel, t as BreadcrumbItem, tt as PageFragmentContribution } from "../types-Vg6f5i3s.mjs";
2
+ import { n as SeoSettings } from "../types-Blb-MRC3.mjs";
3
3
 
4
4
  //#region src/page/context.d.ts
5
5
  /** Fields shared by both input forms */
@@ -1,4 +1,4 @@
1
- import { t as apiError } from "./error-DupyJZE2.mjs";
1
+ import { t as apiError } from "./error-D9w94fjV.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/api/parse.ts
@@ -78,4 +78,4 @@ function isParseError(result) {
78
78
 
79
79
  //#endregion
80
80
  export { parseQuery as i, parseBody as n, parseOptionalBody as r, isParseError as t };
81
- //# sourceMappingURL=parse-DFbuc-Fh.mjs.map
81
+ //# sourceMappingURL=parse-BRJg1kz8.mjs.map