dineway 0.1.9 → 0.1.11

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 (689) hide show
  1. package/README.md +63 -17
  2. package/dist/activity-events-BsMaXdJa.mjs +540 -0
  3. package/dist/allowed-origins-DG86sH8U.mjs +68 -0
  4. package/dist/api/route-utils.d.mts +41 -0
  5. package/dist/api/route-utils.mjs +26 -0
  6. package/dist/api/schemas/index.d.mts +3 -0
  7. package/dist/api/schemas/index.mjs +6 -0
  8. package/dist/api/schemas/setup.d.mts +42 -0
  9. package/dist/api/schemas/setup.mjs +39 -0
  10. package/dist/api-Cmy8Rjk5.mjs +2704 -0
  11. package/dist/api-tokens-Bu3ez1MO.mjs +153 -0
  12. package/dist/api-tokens-DzloJxuh.mjs +3 -0
  13. package/dist/{apply-iVSqz2qs.mjs → apply-Co5imxxT.mjs} +15 -689
  14. package/dist/astro/index.d.mts +10 -6
  15. package/dist/astro/index.mjs +86 -11
  16. package/dist/astro/middleware/auth.d.mts +10 -7
  17. package/dist/astro/middleware/auth.mjs +19 -104
  18. package/dist/astro/middleware/redirect.mjs +24 -14
  19. package/dist/astro/middleware/request-context.mjs +9 -6
  20. package/dist/astro/middleware/setup.mjs +1 -1
  21. package/dist/astro/middleware.mjs +86 -145
  22. package/dist/astro/routes/PluginRegistry.d.mts +14 -0
  23. package/dist/astro/routes/PluginRegistry.mjs +24 -0
  24. package/dist/astro/routes/api/admin/allowed-domains/_domain_.d.mts +14 -0
  25. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +65 -0
  26. package/dist/astro/routes/api/admin/allowed-domains/index.d.mts +14 -0
  27. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +65 -0
  28. package/dist/astro/routes/api/admin/api-tokens/_id_.d.mts +10 -0
  29. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +33 -0
  30. package/dist/astro/routes/api/admin/api-tokens/index.d.mts +16 -0
  31. package/dist/astro/routes/api/admin/api-tokens/index.mjs +59 -0
  32. package/dist/astro/routes/api/admin/briefing.d.mts +7 -0
  33. package/dist/astro/routes/api/admin/briefing.mjs +71 -0
  34. package/dist/astro/routes/api/admin/bylines/_id_/index.d.mts +9 -0
  35. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +74 -0
  36. package/dist/astro/routes/api/admin/bylines/index.d.mts +8 -0
  37. package/dist/astro/routes/api/admin/bylines/index.mjs +61 -0
  38. package/dist/astro/routes/api/admin/comments/_id_/status.d.mts +7 -0
  39. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +80 -0
  40. package/dist/astro/routes/api/admin/comments/_id_.d.mts +14 -0
  41. package/dist/astro/routes/api/admin/comments/_id_.mjs +46 -0
  42. package/dist/astro/routes/api/admin/comments/bulk.d.mts +7 -0
  43. package/dist/astro/routes/api/admin/comments/bulk.mjs +36 -0
  44. package/dist/astro/routes/api/admin/comments/counts.d.mts +7 -0
  45. package/dist/astro/routes/api/admin/comments/counts.mjs +24 -0
  46. package/dist/astro/routes/api/admin/comments/index.d.mts +10 -0
  47. package/dist/astro/routes/api/admin/comments/index.mjs +40 -0
  48. package/dist/astro/routes/api/admin/context/_id_/history.d.mts +7 -0
  49. package/dist/astro/routes/api/admin/context/_id_/history.mjs +45 -0
  50. package/dist/astro/routes/api/admin/context/_id_/index.d.mts +7 -0
  51. package/dist/astro/routes/api/admin/context/_id_/index.mjs +45 -0
  52. package/dist/astro/routes/api/admin/context/_id_/review.d.mts +7 -0
  53. package/dist/astro/routes/api/admin/context/_id_/review.mjs +60 -0
  54. package/dist/astro/routes/api/admin/context/_id_/supersede.d.mts +7 -0
  55. package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +63 -0
  56. package/dist/astro/routes/api/admin/context/diff.d.mts +7 -0
  57. package/dist/astro/routes/api/admin/context/diff.mjs +49 -0
  58. package/dist/astro/routes/api/admin/context/index.d.mts +8 -0
  59. package/dist/astro/routes/api/admin/context/index.mjs +71 -0
  60. package/dist/astro/routes/api/admin/context/stale.d.mts +7 -0
  61. package/dist/astro/routes/api/admin/context/stale.mjs +49 -0
  62. package/dist/astro/routes/api/admin/hitl-requests/_id_/index.d.mts +7 -0
  63. package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +51 -0
  64. package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.d.mts +7 -0
  65. package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +67 -0
  66. package/dist/astro/routes/api/admin/hitl-requests/index.d.mts +7 -0
  67. package/dist/astro/routes/api/admin/hitl-requests/index.mjs +55 -0
  68. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.d.mts +7 -0
  69. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +98 -0
  70. package/dist/astro/routes/api/admin/hooks/exclusive/index.d.mts +7 -0
  71. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +33 -0
  72. package/dist/astro/routes/api/admin/oauth-clients/_id_.d.mts +18 -0
  73. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +79 -0
  74. package/dist/astro/routes/api/admin/oauth-clients/index.d.mts +14 -0
  75. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +58 -0
  76. package/dist/astro/routes/api/admin/plugins/_id_/disable.d.mts +7 -0
  77. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +89 -0
  78. package/dist/astro/routes/api/admin/plugins/_id_/enable.d.mts +7 -0
  79. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +89 -0
  80. package/dist/astro/routes/api/admin/plugins/_id_/index.d.mts +7 -0
  81. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +54 -0
  82. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.d.mts +7 -0
  83. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +98 -0
  84. package/dist/astro/routes/api/admin/plugins/_id_/update.d.mts +7 -0
  85. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +131 -0
  86. package/dist/astro/routes/api/admin/plugins/index.d.mts +7 -0
  87. package/dist/astro/routes/api/admin/plugins/index.mjs +52 -0
  88. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.d.mts +7 -0
  89. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +36 -0
  90. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.d.mts +7 -0
  91. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +54 -0
  92. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.d.mts +7 -0
  93. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +128 -0
  94. package/dist/astro/routes/api/admin/plugins/marketplace/index.d.mts +7 -0
  95. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +61 -0
  96. package/dist/astro/routes/api/admin/plugins/updates.d.mts +7 -0
  97. package/dist/astro/routes/api/admin/plugins/updates.mjs +52 -0
  98. package/dist/astro/routes/api/admin/review-requests/_id_/index.d.mts +7 -0
  99. package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +26 -0
  100. package/dist/astro/routes/api/admin/review-requests/_id_/resolve.d.mts +7 -0
  101. package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +97 -0
  102. package/dist/astro/routes/api/admin/review-requests/index.d.mts +7 -0
  103. package/dist/astro/routes/api/admin/review-requests/index.mjs +31 -0
  104. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.d.mts +7 -0
  105. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +54 -0
  106. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.d.mts +7 -0
  107. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +36 -0
  108. package/dist/astro/routes/api/admin/themes/marketplace/index.d.mts +7 -0
  109. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +70 -0
  110. package/dist/astro/routes/api/admin/users/_id_/disable.d.mts +7 -0
  111. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +38 -0
  112. package/dist/astro/routes/api/admin/users/_id_/enable.d.mts +7 -0
  113. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +29 -0
  114. package/dist/astro/routes/api/admin/users/_id_/index.d.mts +8 -0
  115. package/dist/astro/routes/api/admin/users/_id_/index.mjs +104 -0
  116. package/dist/astro/routes/api/admin/users/_id_/send-recovery.d.mts +7 -0
  117. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +43 -0
  118. package/dist/astro/routes/api/admin/users/index.d.mts +7 -0
  119. package/dist/astro/routes/api/admin/users/index.mjs +54 -0
  120. package/dist/astro/routes/api/auth/dev-bypass.d.mts +8 -0
  121. package/dist/astro/routes/api/auth/dev-bypass.mjs +81 -0
  122. package/dist/astro/routes/api/auth/invite/accept.d.mts +7 -0
  123. package/dist/astro/routes/api/auth/invite/accept.mjs +31 -0
  124. package/dist/astro/routes/api/auth/invite/complete.d.mts +7 -0
  125. package/dist/astro/routes/api/auth/invite/complete.mjs +54 -0
  126. package/dist/astro/routes/api/auth/invite/index.d.mts +7 -0
  127. package/dist/astro/routes/api/auth/invite/index.mjs +51 -0
  128. package/dist/astro/routes/api/auth/invite/register-options.d.mts +7 -0
  129. package/dist/astro/routes/api/auth/invite/register-options.mjs +44 -0
  130. package/dist/astro/routes/api/auth/logout.d.mts +7 -0
  131. package/dist/astro/routes/api/auth/logout.mjs +24 -0
  132. package/dist/astro/routes/api/auth/magic-link/send.d.mts +7 -0
  133. package/dist/astro/routes/api/auth/magic-link/send.mjs +48 -0
  134. package/dist/astro/routes/api/auth/magic-link/verify.d.mts +7 -0
  135. package/dist/astro/routes/api/auth/magic-link/verify.mjs +32 -0
  136. package/dist/astro/routes/api/auth/me.d.mts +13 -0
  137. package/dist/astro/routes/api/auth/me.mjs +41 -0
  138. package/dist/astro/routes/api/auth/mode.d.mts +7 -0
  139. package/dist/astro/routes/api/auth/mode.mjs +28 -0
  140. package/dist/astro/routes/api/auth/oauth/_provider_/callback.d.mts +7 -0
  141. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +114 -0
  142. package/dist/astro/routes/api/auth/oauth/_provider_.d.mts +7 -0
  143. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +58 -0
  144. package/dist/astro/routes/api/auth/passkey/_id_.d.mts +14 -0
  145. package/dist/astro/routes/api/auth/passkey/_id_.mjs +62 -0
  146. package/dist/astro/routes/api/auth/passkey/index.d.mts +7 -0
  147. package/dist/astro/routes/api/auth/passkey/index.mjs +25 -0
  148. package/dist/astro/routes/api/auth/passkey/options.d.mts +7 -0
  149. package/dist/astro/routes/api/auth/passkey/options.mjs +46 -0
  150. package/dist/astro/routes/api/auth/passkey/register/options.d.mts +7 -0
  151. package/dist/astro/routes/api/auth/passkey/register/options.mjs +44 -0
  152. package/dist/astro/routes/api/auth/passkey/register/verify.d.mts +7 -0
  153. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +59 -0
  154. package/dist/astro/routes/api/auth/passkey/verify.d.mts +7 -0
  155. package/dist/astro/routes/api/auth/passkey/verify.mjs +47 -0
  156. package/dist/astro/routes/api/auth/signup/complete.d.mts +7 -0
  157. package/dist/astro/routes/api/auth/signup/complete.mjs +55 -0
  158. package/dist/astro/routes/api/auth/signup/request.d.mts +7 -0
  159. package/dist/astro/routes/api/auth/signup/request.mjs +44 -0
  160. package/dist/astro/routes/api/auth/signup/verify.d.mts +7 -0
  161. package/dist/astro/routes/api/auth/signup/verify.mjs +32 -0
  162. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts +14 -0
  163. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +193 -0
  164. package/dist/astro/routes/api/content/_collection_/_id_/compare.d.mts +7 -0
  165. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +17 -0
  166. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.d.mts +7 -0
  167. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +36 -0
  168. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.d.mts +7 -0
  169. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +39 -0
  170. package/dist/astro/routes/api/content/_collection_/_id_/permanent.d.mts +7 -0
  171. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +31 -0
  172. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.d.mts +7 -0
  173. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +78 -0
  174. package/dist/astro/routes/api/content/_collection_/_id_/publish.d.mts +7 -0
  175. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +92 -0
  176. package/dist/astro/routes/api/content/_collection_/_id_/restore.d.mts +7 -0
  177. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +36 -0
  178. package/dist/astro/routes/api/content/_collection_/_id_/revisions.d.mts +7 -0
  179. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +19 -0
  180. package/dist/astro/routes/api/content/_collection_/_id_/schedule.d.mts +8 -0
  181. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +75 -0
  182. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.d.mts +14 -0
  183. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +85 -0
  184. package/dist/astro/routes/api/content/_collection_/_id_/translations.d.mts +7 -0
  185. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +40 -0
  186. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.d.mts +7 -0
  187. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +36 -0
  188. package/dist/astro/routes/api/content/_collection_/_id_.d.mts +9 -0
  189. package/dist/astro/routes/api/content/_collection_/_id_.mjs +114 -0
  190. package/dist/astro/routes/api/content/_collection_/index.d.mts +8 -0
  191. package/dist/astro/routes/api/content/_collection_/index.mjs +74 -0
  192. package/dist/astro/routes/api/content/_collection_/trash.d.mts +7 -0
  193. package/dist/astro/routes/api/content/_collection_/trash.mjs +23 -0
  194. package/dist/astro/routes/api/dashboard.d.mts +7 -0
  195. package/dist/astro/routes/api/dashboard.mjs +26 -0
  196. package/dist/astro/routes/api/dev/emails.d.mts +8 -0
  197. package/dist/astro/routes/api/dev/emails.mjs +17 -0
  198. package/dist/astro/routes/api/health.d.mts +7 -0
  199. package/dist/astro/routes/api/health.mjs +34 -0
  200. package/dist/astro/routes/api/import/probe.d.mts +17 -0
  201. package/dist/astro/routes/api/import/probe.mjs +33 -0
  202. package/dist/astro/routes/api/import/wordpress/analyze.d.mts +87 -0
  203. package/dist/astro/routes/api/import/wordpress/analyze.mjs +305 -0
  204. package/dist/astro/routes/api/import/wordpress/execute.d.mts +37 -0
  205. package/dist/astro/routes/api/import/wordpress/execute.mjs +197 -0
  206. package/dist/astro/routes/api/import/wordpress/media.d.mts +35 -0
  207. package/dist/astro/routes/api/import/wordpress/media.mjs +222 -0
  208. package/dist/astro/routes/api/import/wordpress/prepare.d.mts +19 -0
  209. package/dist/astro/routes/api/import/wordpress/prepare.mjs +155 -0
  210. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +21 -0
  211. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +289 -0
  212. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +15 -0
  213. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +69 -0
  214. package/dist/astro/routes/api/import/wordpress-plugin/callback.d.mts +7 -0
  215. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +28 -0
  216. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +19 -0
  217. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +268 -0
  218. package/dist/astro/routes/api/manifest.d.mts +7 -0
  219. package/dist/astro/routes/api/manifest.mjs +50 -0
  220. package/dist/astro/routes/api/mcp.d.mts +15 -0
  221. package/dist/astro/routes/api/mcp.mjs +2700 -0
  222. package/dist/astro/routes/api/media/_id_/confirm.d.mts +10 -0
  223. package/dist/astro/routes/api/media/_id_/confirm.mjs +59 -0
  224. package/dist/astro/routes/api/media/_id_.d.mts +22 -0
  225. package/dist/astro/routes/api/media/_id_.mjs +81 -0
  226. package/dist/astro/routes/api/media/file/_...key_.d.mts +7 -0
  227. package/dist/astro/routes/api/media/file/_...key_.mjs +49 -0
  228. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.d.mts +14 -0
  229. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +49 -0
  230. package/dist/astro/routes/api/media/providers/_providerId_/index.d.mts +14 -0
  231. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +72 -0
  232. package/dist/astro/routes/api/media/providers/index.d.mts +10 -0
  233. package/dist/astro/routes/api/media/providers/index.mjs +18 -0
  234. package/dist/astro/routes/api/media/upload-url.d.mts +10 -0
  235. package/dist/astro/routes/api/media/upload-url.mjs +82 -0
  236. package/dist/astro/routes/api/media.d.mts +16 -0
  237. package/dist/astro/routes/api/media.mjs +137 -0
  238. package/dist/astro/routes/api/menus/_name_/items.d.mts +9 -0
  239. package/{src/astro/routes/api/menus/[name]/items.ts → dist/astro/routes/api/menus/_name_/items.mjs} +63 -105
  240. package/dist/astro/routes/api/menus/_name_/reorder.d.mts +7 -0
  241. package/dist/astro/routes/api/menus/_name_/reorder.mjs +77 -0
  242. package/dist/astro/routes/api/menus/_name_.d.mts +9 -0
  243. package/dist/astro/routes/api/menus/_name_.mjs +123 -0
  244. package/dist/astro/routes/api/menus/index.d.mts +8 -0
  245. package/dist/astro/routes/api/menus/index.mjs +84 -0
  246. package/dist/astro/routes/api/oauth/authorize.d.mts +8 -0
  247. package/dist/astro/routes/api/oauth/authorize.mjs +265 -0
  248. package/dist/astro/routes/api/oauth/device/authorize.d.mts +7 -0
  249. package/dist/astro/routes/api/oauth/device/authorize.mjs +30 -0
  250. package/dist/astro/routes/api/oauth/device/code.d.mts +7 -0
  251. package/dist/astro/routes/api/oauth/device/code.mjs +34 -0
  252. package/dist/astro/routes/api/oauth/device/token.d.mts +7 -0
  253. package/dist/astro/routes/api/oauth/device/token.mjs +45 -0
  254. package/dist/astro/routes/api/oauth/register.d.mts +8 -0
  255. package/dist/astro/routes/api/oauth/register.mjs +115 -0
  256. package/dist/astro/routes/api/oauth/token/refresh.d.mts +7 -0
  257. package/dist/astro/routes/api/oauth/token/refresh.mjs +28 -0
  258. package/dist/astro/routes/api/oauth/token/revoke.d.mts +7 -0
  259. package/dist/astro/routes/api/oauth/token/revoke.mjs +25 -0
  260. package/dist/astro/routes/api/oauth/token.d.mts +8 -0
  261. package/dist/astro/routes/api/oauth/token.mjs +138 -0
  262. package/dist/astro/routes/api/openapi.json.d.mts +7 -0
  263. package/dist/astro/routes/api/openapi.json.mjs +2638 -0
  264. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.d.mts +11 -0
  265. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +77 -0
  266. package/dist/astro/routes/api/redirects/404s/index.d.mts +9 -0
  267. package/dist/astro/routes/api/redirects/404s/index.mjs +62 -0
  268. package/dist/astro/routes/api/redirects/404s/summary.d.mts +7 -0
  269. package/dist/astro/routes/api/redirects/404s/summary.mjs +34 -0
  270. package/dist/astro/routes/api/redirects/_id_.d.mts +9 -0
  271. package/dist/astro/routes/api/redirects/_id_.mjs +152 -0
  272. package/dist/astro/routes/api/redirects/index.d.mts +8 -0
  273. package/dist/astro/routes/api/redirects/index.mjs +97 -0
  274. package/dist/astro/routes/api/revisions/_revisionId_/index.d.mts +7 -0
  275. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +16 -0
  276. package/dist/astro/routes/api/revisions/_revisionId_/restore.d.mts +7 -0
  277. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +23 -0
  278. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.d.mts +9 -0
  279. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +98 -0
  280. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.d.mts +8 -0
  281. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +80 -0
  282. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.d.mts +7 -0
  283. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +67 -0
  284. package/dist/astro/routes/api/schema/collections/_slug_/index.d.mts +9 -0
  285. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +97 -0
  286. package/dist/astro/routes/api/schema/collections/index.d.mts +8 -0
  287. package/dist/astro/routes/api/schema/collections/index.mjs +77 -0
  288. package/dist/astro/routes/api/schema/index.d.mts +7 -0
  289. package/dist/astro/routes/api/schema/index.mjs +79 -0
  290. package/dist/astro/routes/api/schema/orphans/_slug_.d.mts +7 -0
  291. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +58 -0
  292. package/dist/astro/routes/api/schema/orphans/index.d.mts +7 -0
  293. package/dist/astro/routes/api/schema/orphans/index.mjs +53 -0
  294. package/dist/astro/routes/api/search/enable.d.mts +15 -0
  295. package/dist/astro/routes/api/search/enable.mjs +55 -0
  296. package/dist/astro/routes/api/search/index.d.mts +16 -0
  297. package/dist/astro/routes/api/search/index.mjs +52 -0
  298. package/dist/astro/routes/api/search/rebuild.d.mts +13 -0
  299. package/dist/astro/routes/api/search/rebuild.mjs +48 -0
  300. package/dist/astro/routes/api/search/stats.d.mts +10 -0
  301. package/dist/astro/routes/api/search/stats.mjs +28 -0
  302. package/dist/astro/routes/api/search/suggest.d.mts +15 -0
  303. package/dist/astro/routes/api/search/suggest.mjs +43 -0
  304. package/dist/astro/routes/api/sections/_slug_.d.mts +9 -0
  305. package/dist/astro/routes/api/sections/_slug_.mjs +156 -0
  306. package/dist/astro/routes/api/sections/index.d.mts +8 -0
  307. package/dist/astro/routes/api/sections/index.mjs +99 -0
  308. package/dist/astro/routes/api/settings/email.d.mts +17 -0
  309. package/dist/astro/routes/api/settings/email.mjs +102 -0
  310. package/dist/astro/routes/api/settings.d.mts +20 -0
  311. package/dist/astro/routes/api/settings.mjs +101 -0
  312. package/dist/astro/routes/api/setup/admin-verify.d.mts +7 -0
  313. package/dist/astro/routes/api/setup/admin-verify.mjs +67 -0
  314. package/dist/astro/routes/api/setup/admin.d.mts +7 -0
  315. package/dist/astro/routes/api/setup/admin.mjs +68 -0
  316. package/dist/astro/routes/api/setup/dev-bypass.d.mts +8 -0
  317. package/dist/astro/routes/api/setup/dev-bypass.mjs +137 -0
  318. package/dist/astro/routes/api/setup/dev-reset.d.mts +7 -0
  319. package/dist/astro/routes/api/setup/dev-reset.mjs +22 -0
  320. package/dist/astro/routes/api/setup/index.d.mts +7 -0
  321. package/dist/astro/routes/api/setup/index.mjs +93 -0
  322. package/dist/astro/routes/api/setup/status.d.mts +7 -0
  323. package/dist/astro/routes/api/setup/status.mjs +57 -0
  324. package/dist/astro/routes/api/snapshot.d.mts +7 -0
  325. package/dist/astro/routes/api/snapshot.mjs +227 -0
  326. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.d.mts +18 -0
  327. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +189 -0
  328. package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts +14 -0
  329. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +113 -0
  330. package/dist/astro/routes/api/taxonomies/index.d.mts +14 -0
  331. package/dist/astro/routes/api/taxonomies/index.mjs +103 -0
  332. package/dist/astro/routes/api/themes/preview.d.mts +7 -0
  333. package/dist/astro/routes/api/themes/preview.mjs +47 -0
  334. package/dist/astro/routes/api/typegen.d.mts +17 -0
  335. package/dist/astro/routes/api/typegen.mjs +75 -0
  336. package/dist/astro/routes/api/well-known/auth.d.mts +7 -0
  337. package/dist/astro/routes/api/well-known/auth.mjs +42 -0
  338. package/dist/astro/routes/api/well-known/oauth-authorization-server.d.mts +7 -0
  339. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +33 -0
  340. package/dist/astro/routes/api/well-known/oauth-protected-resource.d.mts +7 -0
  341. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +21 -0
  342. package/dist/astro/routes/api/widget-areas/_name_/reorder.d.mts +7 -0
  343. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +88 -0
  344. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.d.mts +8 -0
  345. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +158 -0
  346. package/dist/astro/routes/api/widget-areas/_name_/widgets.d.mts +7 -0
  347. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +104 -0
  348. package/dist/astro/routes/api/widget-areas/_name_.d.mts +8 -0
  349. package/dist/astro/routes/api/widget-areas/_name_.mjs +99 -0
  350. package/dist/astro/routes/api/widget-areas/index.d.mts +8 -0
  351. package/dist/astro/routes/api/widget-areas/index.mjs +108 -0
  352. package/dist/astro/routes/api/widget-components.d.mts +7 -0
  353. package/dist/astro/routes/api/widget-components.mjs +15 -0
  354. package/dist/astro/routes/robots.txt.d.mts +7 -0
  355. package/dist/astro/routes/robots.txt.mjs +60 -0
  356. package/dist/astro/routes/sitemap-_collection_.xml.d.mts +7 -0
  357. package/dist/astro/routes/sitemap-_collection_.xml.mjs +70 -0
  358. package/dist/astro/routes/sitemap.xml.d.mts +7 -0
  359. package/dist/astro/routes/sitemap.xml.mjs +63 -0
  360. package/dist/astro/types.d.mts +41 -9
  361. package/dist/auth/providers/github-admin.d.mts +9 -0
  362. package/dist/auth/providers/github-admin.mjs +27 -0
  363. package/dist/auth/providers/github.d.mts +12 -0
  364. package/dist/auth/providers/github.mjs +17 -0
  365. package/dist/auth/providers/google-admin.d.mts +9 -0
  366. package/dist/auth/providers/google-admin.mjs +43 -0
  367. package/dist/auth/providers/google.d.mts +12 -0
  368. package/dist/auth/providers/google.mjs +17 -0
  369. package/dist/auth-control-guard-DKUe_1oa.mjs +13 -0
  370. package/dist/authorize-BBj8C6Y8.mjs +36 -0
  371. package/dist/briefing-BrXCuMEE.mjs +1294 -0
  372. package/dist/briefing-ClWw4mc9.mjs +29 -0
  373. package/dist/{byline-OhH2dlRu.mjs → byline-naZxOPSa.mjs} +3 -3
  374. package/dist/{bylines-BGpD9_hy.mjs → bylines-BcOPh6Ej.mjs} +20 -53
  375. package/dist/bylines-HfUKum_j.d.mts +2023 -0
  376. package/dist/{cache-BdSY-gQN.mjs → cache-DEbQ13c9.mjs} +21 -11
  377. package/dist/challenge-store-DHMgBGOq.mjs +48 -0
  378. package/dist/cli/index.mjs +142 -22
  379. package/dist/client/external-auth-headers.d.mts +1 -1
  380. package/dist/client/index.d.mts +1 -1
  381. package/dist/client/index.mjs +3 -3
  382. package/dist/comment-DFO-gWDH.mjs +246 -0
  383. package/dist/comments-Gy3zLBaP.mjs +186 -0
  384. package/dist/components-DND2rd3D.mjs +107 -0
  385. package/dist/{content-DWi4d0rT.mjs → content-CyLkb-qH.mjs} +33 -44
  386. package/dist/context-bE5Kyvcj.mjs +184 -0
  387. package/dist/context-nxMyOe3p.mjs +849 -0
  388. package/dist/context-route-helpers-D-6uCQ0S.mjs +45 -0
  389. package/dist/context-types-C-LwdAxx.mjs +23 -0
  390. package/dist/cron-DGzVTtJp.mjs +263 -0
  391. package/dist/dashboard-DqnYU8EU.mjs +120 -0
  392. package/dist/db/index.d.mts +3 -3
  393. package/dist/db/libsql.d.mts +1 -1
  394. package/dist/db/libsql.mjs +3 -3
  395. package/dist/db/postgres.d.mts +1 -1
  396. package/dist/db/sqlite.d.mts +1 -1
  397. package/dist/db/sqlite.mjs +1 -2
  398. package/dist/device-flow-7AhWNwCK.mjs +487 -0
  399. package/dist/email-console-CgLVZbcn.mjs +36 -0
  400. package/dist/entity-aliases-C0v-yNET.mjs +51 -0
  401. package/dist/error-DEGjx2Xw.mjs +435 -0
  402. package/dist/escape-mNZr4t2A.mjs +8 -0
  403. package/dist/experimental-workflows-DldxJlqV.mjs +38 -0
  404. package/dist/fts-manager-B1pTNEG_.mjs +297 -0
  405. package/dist/hash-CDX7M0ze.mjs +32 -0
  406. package/dist/hitl-requests-Bx3Bkk9l.mjs +118 -0
  407. package/dist/hitl-route-helpers-DMmJRS7B.mjs +96 -0
  408. package/dist/import-DD3f2jkc.mjs +243 -0
  409. package/dist/import-DVZcYlDp.mjs +1323 -0
  410. package/dist/index-CkljPf5F.d.mts +227 -0
  411. package/dist/index.d.mts +15 -11
  412. package/dist/index.mjs +60 -22
  413. package/dist/{loader-sMG4TZ-u.mjs → loader-PZnPxFLc.mjs} +42 -5
  414. package/dist/{manifest-schema-D1MSVnoI.mjs → manifest-schema-DYoCQ5np.mjs} +22 -10
  415. package/dist/media/index.d.mts +1 -1
  416. package/dist/media/index.mjs +2 -1
  417. package/dist/media/local-runtime.d.mts +11 -7
  418. package/dist/media/local-runtime.mjs +3 -3
  419. package/dist/{media-DMTr80Gv.mjs → media-_7Fxdu45.mjs} +1 -1
  420. package/dist/menus-BacxVCCo.mjs +312 -0
  421. package/dist/menus-CrzHokKj.mjs +3502 -0
  422. package/dist/normalize-C49G_o1k.mjs +126 -0
  423. package/dist/oauth-authorization-C1qiw4hd.mjs +283 -0
  424. package/dist/oauth-clients-CvWatf5p.mjs +298 -0
  425. package/dist/oauth-state-store-hSdzxsEe.mjs +48 -0
  426. package/dist/oauth-user-lookup-B4OcmsLV.mjs +25 -0
  427. package/dist/options-z8VVg1Ll.mjs +114 -0
  428. package/dist/page/index.d.mts +2 -2
  429. package/dist/parse-BeQXIt1U.mjs +88 -0
  430. package/dist/passkey-config-Daqs5fjq.mjs +42 -0
  431. package/dist/{patterns-CrCYkMBb.mjs → patterns-K0DLqWir.mjs} +53 -1
  432. package/dist/{placeholder-Cp8g5Emj.mjs → placeholder-C2P5fKa4.mjs} +1 -126
  433. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -5
  434. package/dist/plugins/adapt-sandbox-entry.mjs +4 -4
  435. package/dist/preview-C_4DyVox.mjs +788 -0
  436. package/dist/public-url-BB_umF5G.mjs +71 -0
  437. package/dist/{query-kDmwCsHh.mjs → query-RiobVwB5.mjs} +93 -19
  438. package/dist/rate-limit-CbJoj_fT.mjs +112 -0
  439. package/dist/{redirect-DnEWAkVg.mjs → redirect-CGl64yOX.mjs} +9 -5
  440. package/dist/redirect-ClSmMOtC.mjs +16 -0
  441. package/dist/redirects-B69T59hK.mjs +499 -0
  442. package/dist/redirects-CqaxraTO.mjs +1070 -0
  443. package/dist/{registry-C0zjeB9P.mjs → registry-C-_hxLqa.mjs} +26 -294
  444. package/dist/request-meta-Bd0mQfiS.mjs +130 -0
  445. package/dist/review-requests-C2DIHwlJ.mjs +148 -0
  446. package/dist/review-requests-DIyjw-K_.mjs +79 -0
  447. package/dist/{runner-CFI6B6J2.d.mts → runner-9eIQXuc2.d.mts} +1 -1
  448. package/dist/{index-yvc6E_17.d.mts → runtime-C4-7y7xK.d.mts} +1539 -2007
  449. package/dist/runtime.d.mts +10 -6
  450. package/dist/runtime.mjs +3 -3
  451. package/dist/schema-BNpI53of.mjs +40 -0
  452. package/dist/search-DM6CVti3.mjs +337 -0
  453. package/dist/secrets-dI8zzTV7.mjs +160 -0
  454. package/dist/sections-DZFyAQXd.mjs +338 -0
  455. package/dist/seed/index.d.mts +2 -2
  456. package/dist/seed/index.mjs +18 -13
  457. package/dist/seo/index.d.mts +1 -1
  458. package/dist/seo-BBgTCOYU.mjs +85 -0
  459. package/dist/seo-CUQctrog.mjs +129 -0
  460. package/dist/service-CSfcQguB.mjs +194 -0
  461. package/dist/settings-4XnpVMOS.mjs +223 -0
  462. package/dist/settings-Bw93cLfe.mjs +50 -0
  463. package/dist/setup-complete-DidsDQ1e.mjs +21 -0
  464. package/dist/setup-nonce-pml1PMKo.mjs +17 -0
  465. package/dist/sidecar-client-vzwV98K4.mjs +66 -0
  466. package/dist/site-activity-B8FjLIVh.mjs +104 -0
  467. package/dist/site-context-Bpu_Paur.mjs +4122 -0
  468. package/dist/site-url-CYIcO0Tj.mjs +12 -0
  469. package/dist/slugify-PDTDtMXp.mjs +30 -0
  470. package/dist/ssrf-CmM76lLV.mjs +248 -0
  471. package/dist/storage/local.d.mts +1 -1
  472. package/dist/storage/local.mjs +1 -1
  473. package/dist/storage/s3.d.mts +1 -1
  474. package/dist/storage/s3.mjs +2 -2
  475. package/dist/{taxonomies-1s5PaS_8.mjs → taxonomies-BvBgfzn3.mjs} +11 -7
  476. package/dist/taxonomies-CpqGcIJD.mjs +355 -0
  477. package/dist/taxonomy-D5cbhc8u.mjs +165 -0
  478. package/dist/{tokens-CJz9ubV6.mjs → tokens-DLTo4dO2.mjs} +1 -1
  479. package/dist/{transport-DB5eDN4x.mjs → transport-C9e_h-BF.mjs} +5 -4
  480. package/dist/trusted-proxy-Bi0Cuk5n.mjs +30 -0
  481. package/dist/{types-BawVha09.mjs → types-Bs6lTBBW.mjs} +1 -1
  482. package/dist/types-C982qI5I.d.mts +344 -0
  483. package/dist/types-D4XVOt01.d.mts +165 -0
  484. package/dist/{types-Cj0KMIZV.d.mts → types-DgfUZqcd.d.mts} +54 -16
  485. package/dist/{types-BuMDPy5C.d.mts → types-IPACEM14.d.mts} +6 -0
  486. package/dist/user-CcXq-zoL.mjs +154 -0
  487. package/dist/utils-D2in-zwy.mjs +285 -0
  488. package/dist/{validate-BZ5wnLLp.mjs → validate-BJgA6TW_.mjs} +1 -1
  489. package/dist/{validate-IPf8n4Fj.d.mts → validate-JCZihRIa.d.mts} +3 -3
  490. package/dist/version-DH53KCQd.mjs +6 -0
  491. package/dist/widgets-B7Q_7bxN.mjs +104 -0
  492. package/dist/wordpress-slugs-BevajWrC.mjs +14 -0
  493. package/dist/zod-generator-DBVP8D0P.mjs +132 -0
  494. package/locals.d.ts +1 -6
  495. package/package.json +67 -11
  496. package/src/components/DinewayHead.astro +8 -4
  497. package/src/components/DinewayImage.astro +7 -5
  498. package/src/components/DinewayMedia.astro +9 -3
  499. package/src/components/Gallery.astro +5 -3
  500. package/src/components/Image.astro +5 -1
  501. package/src/components/InlinePortableTextEditor.tsx +68 -19
  502. package/dist/error-BmL6QipT.mjs +0 -30
  503. package/dist/search-DxopAWxs.mjs +0 -11200
  504. package/dist/version-BPz1imu2.mjs +0 -6
  505. package/src/astro/routes/PluginRegistry.tsx +0 -21
  506. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +0 -112
  507. package/src/astro/routes/api/admin/allowed-domains/index.ts +0 -108
  508. package/src/astro/routes/api/admin/api-tokens/[id].ts +0 -44
  509. package/src/astro/routes/api/admin/api-tokens/index.ts +0 -90
  510. package/src/astro/routes/api/admin/briefing.ts +0 -76
  511. package/src/astro/routes/api/admin/bylines/[id]/index.ts +0 -90
  512. package/src/astro/routes/api/admin/bylines/index.ts +0 -74
  513. package/src/astro/routes/api/admin/comments/[id]/status.ts +0 -120
  514. package/src/astro/routes/api/admin/comments/[id].ts +0 -64
  515. package/src/astro/routes/api/admin/comments/bulk.ts +0 -42
  516. package/src/astro/routes/api/admin/comments/counts.ts +0 -30
  517. package/src/astro/routes/api/admin/comments/index.ts +0 -46
  518. package/src/astro/routes/api/admin/context/[id]/history.ts +0 -35
  519. package/src/astro/routes/api/admin/context/[id]/index.ts +0 -35
  520. package/src/astro/routes/api/admin/context/[id]/review.ts +0 -57
  521. package/src/astro/routes/api/admin/context/[id]/supersede.ts +0 -58
  522. package/src/astro/routes/api/admin/context/diff.ts +0 -35
  523. package/src/astro/routes/api/admin/context/index.ts +0 -69
  524. package/src/astro/routes/api/admin/context/stale.ts +0 -35
  525. package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +0 -38
  526. package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +0 -54
  527. package/src/astro/routes/api/admin/hitl-requests/index.ts +0 -38
  528. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +0 -132
  529. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +0 -51
  530. package/src/astro/routes/api/admin/oauth-clients/[id].ts +0 -137
  531. package/src/astro/routes/api/admin/oauth-clients/index.ts +0 -95
  532. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +0 -91
  533. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +0 -91
  534. package/src/astro/routes/api/admin/plugins/[id]/index.ts +0 -38
  535. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +0 -98
  536. package/src/astro/routes/api/admin/plugins/[id]/update.ts +0 -154
  537. package/src/astro/routes/api/admin/plugins/index.ts +0 -32
  538. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +0 -62
  539. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +0 -33
  540. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +0 -135
  541. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +0 -38
  542. package/src/astro/routes/api/admin/plugins/updates.ts +0 -28
  543. package/src/astro/routes/api/admin/review-requests/[id]/index.ts +0 -35
  544. package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +0 -52
  545. package/src/astro/routes/api/admin/review-requests/index.ts +0 -35
  546. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +0 -33
  547. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +0 -62
  548. package/src/astro/routes/api/admin/themes/marketplace/index.ts +0 -45
  549. package/src/astro/routes/api/admin/users/[id]/disable.ts +0 -72
  550. package/src/astro/routes/api/admin/users/[id]/enable.ts +0 -48
  551. package/src/astro/routes/api/admin/users/[id]/index.ts +0 -166
  552. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +0 -72
  553. package/src/astro/routes/api/admin/users/index.ts +0 -66
  554. package/src/astro/routes/api/auth/dev-bypass.ts +0 -139
  555. package/src/astro/routes/api/auth/invite/accept.ts +0 -52
  556. package/src/astro/routes/api/auth/invite/complete.ts +0 -86
  557. package/src/astro/routes/api/auth/invite/index.ts +0 -99
  558. package/src/astro/routes/api/auth/invite/register-options.ts +0 -73
  559. package/src/astro/routes/api/auth/logout.ts +0 -40
  560. package/src/astro/routes/api/auth/magic-link/send.ts +0 -90
  561. package/src/astro/routes/api/auth/magic-link/verify.ts +0 -71
  562. package/src/astro/routes/api/auth/me.ts +0 -60
  563. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +0 -221
  564. package/src/astro/routes/api/auth/oauth/[provider].ts +0 -120
  565. package/src/astro/routes/api/auth/passkey/[id].ts +0 -124
  566. package/src/astro/routes/api/auth/passkey/index.ts +0 -54
  567. package/src/astro/routes/api/auth/passkey/options.ts +0 -85
  568. package/src/astro/routes/api/auth/passkey/register/options.ts +0 -88
  569. package/src/astro/routes/api/auth/passkey/register/verify.ts +0 -119
  570. package/src/astro/routes/api/auth/passkey/verify.ts +0 -72
  571. package/src/astro/routes/api/auth/signup/complete.ts +0 -87
  572. package/src/astro/routes/api/auth/signup/request.ts +0 -89
  573. package/src/astro/routes/api/auth/signup/verify.ts +0 -53
  574. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +0 -310
  575. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +0 -28
  576. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +0 -68
  577. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +0 -77
  578. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +0 -42
  579. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +0 -107
  580. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +0 -100
  581. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +0 -64
  582. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +0 -31
  583. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +0 -129
  584. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +0 -143
  585. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +0 -50
  586. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +0 -69
  587. package/src/astro/routes/api/content/[collection]/[id].ts +0 -173
  588. package/src/astro/routes/api/content/[collection]/index.ts +0 -103
  589. package/src/astro/routes/api/content/[collection]/trash.ts +0 -33
  590. package/src/astro/routes/api/dashboard.ts +0 -32
  591. package/src/astro/routes/api/dev/emails.ts +0 -36
  592. package/src/astro/routes/api/health.ts +0 -54
  593. package/src/astro/routes/api/import/probe.ts +0 -47
  594. package/src/astro/routes/api/import/wordpress/analyze.ts +0 -523
  595. package/src/astro/routes/api/import/wordpress/execute.ts +0 -330
  596. package/src/astro/routes/api/import/wordpress/media.ts +0 -338
  597. package/src/astro/routes/api/import/wordpress/prepare.ts +0 -212
  598. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +0 -425
  599. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +0 -111
  600. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +0 -58
  601. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +0 -399
  602. package/src/astro/routes/api/manifest.ts +0 -75
  603. package/src/astro/routes/api/mcp.ts +0 -125
  604. package/src/astro/routes/api/media/[id]/confirm.ts +0 -93
  605. package/src/astro/routes/api/media/[id].ts +0 -145
  606. package/src/astro/routes/api/media/file/[...key].ts +0 -79
  607. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +0 -91
  608. package/src/astro/routes/api/media/providers/[providerId]/index.ts +0 -111
  609. package/src/astro/routes/api/media/providers/index.ts +0 -30
  610. package/src/astro/routes/api/media/upload-url.ts +0 -146
  611. package/src/astro/routes/api/media.ts +0 -204
  612. package/src/astro/routes/api/menus/[name]/reorder.ts +0 -79
  613. package/src/astro/routes/api/menus/[name].ts +0 -145
  614. package/src/astro/routes/api/menus/index.ts +0 -91
  615. package/src/astro/routes/api/oauth/authorize.ts +0 -430
  616. package/src/astro/routes/api/oauth/device/authorize.ts +0 -45
  617. package/src/astro/routes/api/oauth/device/code.ts +0 -56
  618. package/src/astro/routes/api/oauth/device/token.ts +0 -70
  619. package/src/astro/routes/api/oauth/register.ts +0 -182
  620. package/src/astro/routes/api/oauth/token/refresh.ts +0 -38
  621. package/src/astro/routes/api/oauth/token/revoke.ts +0 -38
  622. package/src/astro/routes/api/oauth/token.ts +0 -195
  623. package/src/astro/routes/api/openapi.json.ts +0 -33
  624. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +0 -109
  625. package/src/astro/routes/api/redirects/404s/index.ts +0 -72
  626. package/src/astro/routes/api/redirects/404s/summary.ts +0 -33
  627. package/src/astro/routes/api/redirects/[id].ts +0 -183
  628. package/src/astro/routes/api/redirects/index.ts +0 -100
  629. package/src/astro/routes/api/revisions/[revisionId]/index.ts +0 -29
  630. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +0 -62
  631. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -104
  632. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -67
  633. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -45
  634. package/src/astro/routes/api/schema/collections/[slug]/index.ts +0 -107
  635. package/src/astro/routes/api/schema/collections/index.ts +0 -61
  636. package/src/astro/routes/api/schema/index.ts +0 -109
  637. package/src/astro/routes/api/schema/orphans/[slug].ts +0 -36
  638. package/src/astro/routes/api/schema/orphans/index.ts +0 -26
  639. package/src/astro/routes/api/search/enable.ts +0 -64
  640. package/src/astro/routes/api/search/index.ts +0 -52
  641. package/src/astro/routes/api/search/rebuild.ts +0 -72
  642. package/src/astro/routes/api/search/stats.ts +0 -35
  643. package/src/astro/routes/api/search/suggest.ts +0 -50
  644. package/src/astro/routes/api/sections/[slug].ts +0 -203
  645. package/src/astro/routes/api/sections/index.ts +0 -107
  646. package/src/astro/routes/api/settings/email.ts +0 -150
  647. package/src/astro/routes/api/settings.ts +0 -116
  648. package/src/astro/routes/api/setup/admin-verify.ts +0 -122
  649. package/src/astro/routes/api/setup/admin.ts +0 -104
  650. package/src/astro/routes/api/setup/dev-bypass.ts +0 -200
  651. package/src/astro/routes/api/setup/dev-reset.ts +0 -40
  652. package/src/astro/routes/api/setup/index.ts +0 -128
  653. package/src/astro/routes/api/setup/status.ts +0 -122
  654. package/src/astro/routes/api/snapshot.ts +0 -76
  655. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +0 -232
  656. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +0 -131
  657. package/src/astro/routes/api/taxonomies/index.ts +0 -114
  658. package/src/astro/routes/api/themes/preview.ts +0 -78
  659. package/src/astro/routes/api/typegen.ts +0 -114
  660. package/src/astro/routes/api/well-known/auth.ts +0 -71
  661. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +0 -48
  662. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +0 -39
  663. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +0 -114
  664. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +0 -213
  665. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +0 -126
  666. package/src/astro/routes/api/widget-areas/[name].ts +0 -135
  667. package/src/astro/routes/api/widget-areas/index.ts +0 -149
  668. package/src/astro/routes/api/widget-components.ts +0 -22
  669. package/src/astro/routes/robots.txt.ts +0 -81
  670. package/src/astro/routes/sitemap-[collection].xml.ts +0 -104
  671. package/src/astro/routes/sitemap.xml.ts +0 -92
  672. /package/dist/{adapters-C2ypTrZZ.d.mts → adapters-BLDldpJg.d.mts} +0 -0
  673. /package/{src → dist}/astro/routes/admin.astro +0 -0
  674. /package/dist/{base64-F8-DUraK.mjs → base64-Cz-aU0X1.mjs} +0 -0
  675. /package/dist/{chunks--4F8ddV4.mjs → chunks-D_jVet6z.mjs} +0 -0
  676. /package/dist/{config-BXwuX8Bx.mjs → config-CAMFxGaV.mjs} +0 -0
  677. /package/dist/{db-errors-CEqD7qH9.mjs → db-errors-DKUg_NgF.mjs} +0 -0
  678. /package/dist/{default-VjJyuuG9.mjs → default-C3PZN-bz.mjs} +0 -0
  679. /package/dist/{load-Coc9HpHH.mjs → load-D-9NhLmF.mjs} +0 -0
  680. /package/dist/{mode-47goXBBK.mjs → mode-C80mAZQv.mjs} +0 -0
  681. /package/dist/{placeholder--wOi4TbO.d.mts → placeholder-CHkLckzK.d.mts} +0 -0
  682. /package/dist/{request-cache-Dk5qPSOx.mjs → request-cache-DHMRr2Lf.mjs} +0 -0
  683. /package/dist/{transaction-Cn2rjY78.mjs → transaction-x2tJQ-A1.mjs} +0 -0
  684. /package/dist/{transport-Wge_IzKl.d.mts → transport-6RefuBdV.d.mts} +0 -0
  685. /package/dist/{types-griIBQOQ.mjs → types-B9gKVOHk.mjs} +0 -0
  686. /package/dist/{types-CWbdtiux.d.mts → types-B9qVtiHb.d.mts} +0 -0
  687. /package/dist/{types-COeOq9nK.mjs → types-DL7Y8D_t.mjs} +0 -0
  688. /package/dist/{types-BzcUjoqg.d.mts → types-Djdp0cZO.d.mts} +0 -0
  689. /package/dist/{types-DOrVigru.d.mts → types-Du8jreyC.d.mts} +0 -0
@@ -0,0 +1,3502 @@
1
+ import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
2
+ import { t as CommentRepository } from "./comment-DFO-gWDH.mjs";
3
+ import { t as OptionsRepository } from "./options-z8VVg1Ll.mjs";
4
+ import { n as createPluginContext, t as PluginContextFactory } from "./context-nxMyOe3p.mjs";
5
+ import { r as getDb } from "./loader-PZnPxFLc.mjs";
6
+ import { n as requestCached } from "./request-cache-DHMRr2Lf.mjs";
7
+ import { lt as sanitizeHref } from "./redirects-CqaxraTO.mjs";
8
+ import { t as extractRequestMeta } from "./request-meta-Bd0mQfiS.mjs";
9
+ import { r as setCronTasksEnabled } from "./cron-DGzVTtJp.mjs";
10
+ import { sql } from "kysely";
11
+ import { AsyncLocalStorage } from "node:async_hooks";
12
+ import { ulid } from "ulidx";
13
+ import { z } from "astro/zod";
14
+ import { Worker } from "node:worker_threads";
15
+
16
+ //#region src/fields/image.ts
17
+ /**
18
+ * Image field schema
19
+ */
20
+ const imageSchema = z.object({
21
+ id: z.string(),
22
+ src: z.string(),
23
+ alt: z.string().optional(),
24
+ width: z.number().optional(),
25
+ height: z.number().optional()
26
+ });
27
+ /**
28
+ * Image field
29
+ * References media items from the media library
30
+ */
31
+ function image(options) {
32
+ return {
33
+ type: "image",
34
+ columnType: "TEXT",
35
+ schema: options?.required === false ? imageSchema.optional() : imageSchema,
36
+ options,
37
+ ui: { widget: "image" }
38
+ };
39
+ }
40
+
41
+ //#endregion
42
+ //#region src/fields/reference.ts
43
+ /**
44
+ * Reference field
45
+ * References another content item by ID
46
+ */
47
+ function reference(collection, options) {
48
+ const schema = z.string();
49
+ return {
50
+ type: "reference",
51
+ columnType: "TEXT",
52
+ schema: options?.required === false ? schema.optional() : schema,
53
+ options: {
54
+ ...options,
55
+ collection
56
+ },
57
+ ui: { widget: "reference" }
58
+ };
59
+ }
60
+
61
+ //#endregion
62
+ //#region src/fields/portable-text.ts
63
+ /**
64
+ * Portable Text block schema
65
+ */
66
+ const portableTextBlockSchema = z.object({
67
+ _type: z.string(),
68
+ _key: z.string()
69
+ }).passthrough();
70
+ /**
71
+ * Portable Text field
72
+ * Stores structured content in Portable Text format
73
+ */
74
+ function portableText(options) {
75
+ const schema = z.array(portableTextBlockSchema);
76
+ return {
77
+ type: "portableText",
78
+ columnType: "JSON",
79
+ schema: options?.required === false ? schema.optional() : schema,
80
+ options,
81
+ ui: { widget: "portableText" }
82
+ };
83
+ }
84
+
85
+ //#endregion
86
+ //#region src/content/converters/prosemirror-to-portable-text.ts
87
+ /**
88
+ * Generate a unique key for Portable Text blocks
89
+ */
90
+ function generateKey() {
91
+ return Math.random().toString(36).substring(2, 11);
92
+ }
93
+ /**
94
+ * Convert ProseMirror document to Portable Text
95
+ */
96
+ function prosemirrorToPortableText(doc) {
97
+ if (!doc || doc.type !== "doc" || !doc.content) return [];
98
+ const blocks = [];
99
+ for (const node of doc.content) {
100
+ const converted = convertNode(node);
101
+ if (converted) if (Array.isArray(converted)) blocks.push(...converted);
102
+ else blocks.push(converted);
103
+ }
104
+ return blocks;
105
+ }
106
+ /**
107
+ * Convert a single ProseMirror node to Portable Text block(s)
108
+ */
109
+ function convertNode(node) {
110
+ switch (node.type) {
111
+ case "paragraph": return convertParagraph(node);
112
+ case "heading": return convertHeading(node);
113
+ case "bulletList": return convertList$1(node, "bullet");
114
+ case "orderedList": return convertList$1(node, "number");
115
+ case "blockquote": return convertBlockquote(node);
116
+ case "codeBlock": return convertCodeBlock$1(node);
117
+ case "image": return convertImage$1(node);
118
+ case "horizontalRule": return {
119
+ _type: "break",
120
+ _key: generateKey(),
121
+ style: "lineBreak"
122
+ };
123
+ default: return {
124
+ _type: node.type,
125
+ _key: generateKey(),
126
+ ...node.attrs,
127
+ _pmContent: node.content
128
+ };
129
+ }
130
+ }
131
+ /**
132
+ * Convert paragraph to Portable Text block
133
+ */
134
+ function convertParagraph(node) {
135
+ const { children, markDefs } = convertInlineContent(node.content || []);
136
+ if (children.length === 0) return null;
137
+ return {
138
+ _type: "block",
139
+ _key: generateKey(),
140
+ style: "normal",
141
+ children,
142
+ markDefs: markDefs.length > 0 ? markDefs : void 0
143
+ };
144
+ }
145
+ /** Map heading level number to Portable Text style */
146
+ function headingLevelToStyle(level) {
147
+ switch (level) {
148
+ case 1: return "h1";
149
+ case 2: return "h2";
150
+ case 3: return "h3";
151
+ case 4: return "h4";
152
+ case 5: return "h5";
153
+ case 6: return "h6";
154
+ default: return "h1";
155
+ }
156
+ }
157
+ /**
158
+ * Convert heading to Portable Text block
159
+ */
160
+ function convertHeading(node) {
161
+ const { children, markDefs } = convertInlineContent(node.content || []);
162
+ const style = headingLevelToStyle(typeof node.attrs?.level === "number" ? node.attrs.level : 1);
163
+ if (children.length === 0) return null;
164
+ return {
165
+ _type: "block",
166
+ _key: generateKey(),
167
+ style,
168
+ children,
169
+ markDefs: markDefs.length > 0 ? markDefs : void 0
170
+ };
171
+ }
172
+ /**
173
+ * Convert list to Portable Text blocks
174
+ */
175
+ function convertList$1(node, listItem) {
176
+ const blocks = [];
177
+ for (const item of node.content || []) if (item.type === "listItem") {
178
+ const itemBlocks = convertListItem$1(item, listItem, 1);
179
+ blocks.push(...itemBlocks);
180
+ }
181
+ return blocks;
182
+ }
183
+ /**
184
+ * Convert list item to Portable Text blocks
185
+ */
186
+ function convertListItem$1(item, listItem, level) {
187
+ const blocks = [];
188
+ for (const child of item.content || []) if (child.type === "paragraph") {
189
+ const { children, markDefs } = convertInlineContent(child.content || []);
190
+ if (children.length > 0) blocks.push({
191
+ _type: "block",
192
+ _key: generateKey(),
193
+ style: "normal",
194
+ listItem,
195
+ level,
196
+ children,
197
+ markDefs: markDefs.length > 0 ? markDefs : void 0
198
+ });
199
+ } else if (child.type === "bulletList") blocks.push(...convertListItemNested(child, "bullet", level + 1));
200
+ else if (child.type === "orderedList") blocks.push(...convertListItemNested(child, "number", level + 1));
201
+ return blocks;
202
+ }
203
+ /**
204
+ * Convert nested list
205
+ */
206
+ function convertListItemNested(node, listItem, level) {
207
+ const blocks = [];
208
+ for (const item of node.content || []) if (item.type === "listItem") blocks.push(...convertListItem$1(item, listItem, level));
209
+ return blocks;
210
+ }
211
+ /**
212
+ * Convert blockquote to Portable Text blocks
213
+ */
214
+ function convertBlockquote(node) {
215
+ const blocks = [];
216
+ for (const child of node.content || []) if (child.type === "paragraph") {
217
+ const { children, markDefs } = convertInlineContent(child.content || []);
218
+ if (children.length > 0) blocks.push({
219
+ _type: "block",
220
+ _key: generateKey(),
221
+ style: "blockquote",
222
+ children,
223
+ markDefs: markDefs.length > 0 ? markDefs : void 0
224
+ });
225
+ }
226
+ return blocks.length === 1 ? blocks[0] : blocks.length > 0 ? blocks : null;
227
+ }
228
+ /**
229
+ * Convert code block to Portable Text
230
+ */
231
+ function convertCodeBlock$1(node) {
232
+ const code = node.content?.map((n) => n.text || "").join("") || "";
233
+ const language = typeof node.attrs?.language === "string" ? node.attrs.language : void 0;
234
+ return {
235
+ _type: "code",
236
+ _key: generateKey(),
237
+ code,
238
+ language: language || void 0
239
+ };
240
+ }
241
+ /**
242
+ * Convert image to Portable Text
243
+ */
244
+ function convertImage$1(node) {
245
+ const attrs = node.attrs;
246
+ const provider = typeof attrs?.provider === "string" ? attrs.provider : void 0;
247
+ const mediaId = typeof attrs?.mediaId === "string" ? attrs.mediaId : void 0;
248
+ const src = typeof attrs?.src === "string" ? attrs.src : "";
249
+ const alt = typeof attrs?.alt === "string" ? attrs.alt : void 0;
250
+ const title = typeof attrs?.title === "string" ? attrs.title : void 0;
251
+ const width = typeof attrs?.width === "number" ? attrs.width : void 0;
252
+ const height = typeof attrs?.height === "number" ? attrs.height : void 0;
253
+ const displayWidth = typeof attrs?.displayWidth === "number" ? attrs.displayWidth : void 0;
254
+ const displayHeight = typeof attrs?.displayHeight === "number" ? attrs.displayHeight : void 0;
255
+ return {
256
+ _type: "image",
257
+ _key: generateKey(),
258
+ asset: {
259
+ _ref: mediaId || src || "",
260
+ url: src || "",
261
+ provider: provider && provider !== "local" ? provider : void 0
262
+ },
263
+ alt: alt || void 0,
264
+ caption: title || void 0,
265
+ width: width || void 0,
266
+ height: height || void 0,
267
+ displayWidth: displayWidth || void 0,
268
+ displayHeight: displayHeight || void 0
269
+ };
270
+ }
271
+ /**
272
+ * Convert inline content (text nodes with marks) to Portable Text spans
273
+ */
274
+ function convertInlineContent(nodes) {
275
+ const children = [];
276
+ const markDefs = [];
277
+ const markDefMap = /* @__PURE__ */ new Map();
278
+ for (const node of nodes) if (node.type === "text" && node.text) {
279
+ const marks = [];
280
+ for (const mark of node.marks || []) {
281
+ const markType = convertMark(mark, markDefs, markDefMap);
282
+ if (markType) marks.push(markType);
283
+ }
284
+ children.push({
285
+ _type: "span",
286
+ _key: generateKey(),
287
+ text: node.text,
288
+ marks: marks.length > 0 ? marks : void 0
289
+ });
290
+ } else if (node.type === "hardBreak") if (children.length > 0) {
291
+ const lastChild = children.at(-1);
292
+ lastChild.text += "\n";
293
+ } else children.push({
294
+ _type: "span",
295
+ _key: generateKey(),
296
+ text: "\n"
297
+ });
298
+ if (children.length === 0) children.push({
299
+ _type: "span",
300
+ _key: generateKey(),
301
+ text: ""
302
+ });
303
+ return {
304
+ children,
305
+ markDefs
306
+ };
307
+ }
308
+ /**
309
+ * Convert a ProseMirror mark to Portable Text mark
310
+ */
311
+ function convertMark(mark, markDefs, markDefMap) {
312
+ switch (mark.type) {
313
+ case "bold":
314
+ case "strong": return "strong";
315
+ case "italic":
316
+ case "em": return "em";
317
+ case "underline": return "underline";
318
+ case "strike":
319
+ case "strikethrough": return "strike-through";
320
+ case "code": return "code";
321
+ case "link": {
322
+ const href = (typeof mark.attrs?.href === "string" ? mark.attrs.href : "") || "";
323
+ if (markDefMap.has(href)) return markDefMap.get(href);
324
+ const key = generateKey();
325
+ markDefs.push({
326
+ _type: "link",
327
+ _key: key,
328
+ href,
329
+ blank: mark.attrs?.target === "_blank"
330
+ });
331
+ markDefMap.set(href, key);
332
+ return key;
333
+ }
334
+ default: return mark.type;
335
+ }
336
+ }
337
+
338
+ //#endregion
339
+ //#region src/content/converters/portable-text-to-prosemirror.ts
340
+ /**
341
+ * Convert Portable Text to ProseMirror document
342
+ */
343
+ function portableTextToProsemirror(blocks) {
344
+ if (!blocks || blocks.length === 0) return {
345
+ type: "doc",
346
+ content: [{ type: "paragraph" }]
347
+ };
348
+ const content = [];
349
+ let i = 0;
350
+ while (i < blocks.length) {
351
+ const block = blocks[i];
352
+ if (isTextBlock(block) && block.listItem) {
353
+ const listBlocks = [];
354
+ const listType = block.listItem;
355
+ while (i < blocks.length) {
356
+ const current = blocks[i];
357
+ if (isTextBlock(current) && current.listItem === listType) {
358
+ listBlocks.push(current);
359
+ i++;
360
+ } else break;
361
+ }
362
+ content.push(convertList(listBlocks, listType));
363
+ } else {
364
+ const converted = convertBlock(block);
365
+ if (converted) content.push(converted);
366
+ i++;
367
+ }
368
+ }
369
+ return {
370
+ type: "doc",
371
+ content: content.length > 0 ? content : [{ type: "paragraph" }]
372
+ };
373
+ }
374
+ /**
375
+ * Type guard for text blocks
376
+ */
377
+ function isTextBlock(block) {
378
+ return block._type === "block";
379
+ }
380
+ /**
381
+ * Type guard for image blocks.
382
+ * Checks both `_type` and that `asset` is a valid object — image blocks
383
+ * without an `asset` wrapper (e.g. `{ _type: "image", url: "..." }`) are
384
+ * malformed and should not be cast to `PortableTextImageBlock`.
385
+ */
386
+ function isImageBlock(block) {
387
+ return block._type === "image" && "asset" in block && typeof block.asset === "object" && block.asset !== null;
388
+ }
389
+ /**
390
+ * Type guard for code blocks
391
+ */
392
+ function isCodeBlock(block) {
393
+ return block._type === "code";
394
+ }
395
+ /**
396
+ * Convert a single Portable Text block to ProseMirror node
397
+ */
398
+ function convertBlock(block) {
399
+ if (isTextBlock(block)) return convertTextBlock(block);
400
+ if (isImageBlock(block)) return convertImage(block);
401
+ if (block._type === "image") return convertMalformedImage(block);
402
+ if (isCodeBlock(block)) return convertCodeBlock(block);
403
+ if (block._type === "break") return { type: "horizontalRule" };
404
+ return {
405
+ type: "paragraph",
406
+ content: [{
407
+ type: "text",
408
+ text: `[Unknown block type: ${block._type}]`,
409
+ marks: [{ type: "code" }]
410
+ }]
411
+ };
412
+ }
413
+ /**
414
+ * Convert text block to ProseMirror paragraph or heading
415
+ */
416
+ function convertTextBlock(block) {
417
+ const { style = "normal", children, markDefs = [] } = block;
418
+ const content = convertSpans(children, markDefs);
419
+ switch (style) {
420
+ case "h1":
421
+ case "h2":
422
+ case "h3":
423
+ case "h4":
424
+ case "h5":
425
+ case "h6": return {
426
+ type: "heading",
427
+ attrs: { level: parseInt(style.substring(1), 10) },
428
+ content: content.length > 0 ? content : void 0
429
+ };
430
+ case "blockquote": return {
431
+ type: "blockquote",
432
+ content: [{
433
+ type: "paragraph",
434
+ content: content.length > 0 ? content : void 0
435
+ }]
436
+ };
437
+ default: return {
438
+ type: "paragraph",
439
+ content: content.length > 0 ? content : void 0
440
+ };
441
+ }
442
+ }
443
+ /**
444
+ * Convert list items to ProseMirror list
445
+ */
446
+ function convertList(items, listType) {
447
+ const rootItems = [];
448
+ let i = 0;
449
+ while (i < items.length) {
450
+ const item = items[i];
451
+ if ((item.level || 1) === 1) {
452
+ const nestedItems = [];
453
+ i++;
454
+ while (i < items.length && (items[i].level || 1) > 1) {
455
+ nestedItems.push(items[i]);
456
+ i++;
457
+ }
458
+ rootItems.push(convertListItem(item, nestedItems, listType));
459
+ } else {
460
+ rootItems.push(convertListItem(item, [], listType));
461
+ i++;
462
+ }
463
+ }
464
+ return {
465
+ type: listType === "bullet" ? "bulletList" : "orderedList",
466
+ content: rootItems
467
+ };
468
+ }
469
+ /**
470
+ * Convert a single list item to ProseMirror
471
+ */
472
+ function convertListItem(item, nestedItems, parentListType) {
473
+ const content = [];
474
+ const spans = convertSpans(item.children, item.markDefs || []);
475
+ content.push({
476
+ type: "paragraph",
477
+ content: spans.length > 0 ? spans : void 0
478
+ });
479
+ if (nestedItems.length > 0) {
480
+ let j = 0;
481
+ while (j < nestedItems.length) {
482
+ const nestedListType = nestedItems[j].listItem || parentListType;
483
+ const nestedGroup = [];
484
+ while (j < nestedItems.length && (nestedItems[j].listItem || parentListType) === nestedListType) {
485
+ nestedGroup.push(nestedItems[j]);
486
+ j++;
487
+ }
488
+ if (nestedGroup.length > 0) {
489
+ const adjustedGroup = nestedGroup.map((ni) => ({
490
+ ...ni,
491
+ level: (ni.level || 2) - 1
492
+ }));
493
+ content.push(convertList(adjustedGroup, nestedListType));
494
+ }
495
+ }
496
+ }
497
+ return {
498
+ type: "listItem",
499
+ content
500
+ };
501
+ }
502
+ /**
503
+ * Convert Portable Text spans to ProseMirror text nodes
504
+ */
505
+ function convertSpans(spans, markDefs) {
506
+ const nodes = [];
507
+ const markDefsMap = new Map(markDefs.map((md) => [md._key, md]));
508
+ for (const span of spans) {
509
+ if (span._type !== "span") continue;
510
+ const parts = span.text.split("\n");
511
+ for (let i = 0; i < parts.length; i++) {
512
+ const text = parts[i];
513
+ if (text.length > 0) {
514
+ const marks = convertMarks(span.marks || [], markDefsMap);
515
+ const node = {
516
+ type: "text",
517
+ text
518
+ };
519
+ if (marks.length > 0) node.marks = marks;
520
+ nodes.push(node);
521
+ }
522
+ if (i < parts.length - 1) nodes.push({ type: "hardBreak" });
523
+ }
524
+ }
525
+ return nodes;
526
+ }
527
+ /**
528
+ * Convert Portable Text marks to ProseMirror marks
529
+ */
530
+ function convertMarks(marks, markDefs) {
531
+ const pmMarks = [];
532
+ for (const mark of marks) switch (mark) {
533
+ case "strong":
534
+ pmMarks.push({ type: "bold" });
535
+ break;
536
+ case "em":
537
+ pmMarks.push({ type: "italic" });
538
+ break;
539
+ case "underline":
540
+ pmMarks.push({ type: "underline" });
541
+ break;
542
+ case "strike-through":
543
+ pmMarks.push({ type: "strike" });
544
+ break;
545
+ case "code":
546
+ pmMarks.push({ type: "code" });
547
+ break;
548
+ default: {
549
+ const markDef = markDefs.get(mark);
550
+ if (markDef) if (markDef._type === "link") pmMarks.push({
551
+ type: "link",
552
+ attrs: {
553
+ href: markDef.href,
554
+ target: markDef.blank ? "_blank" : null
555
+ }
556
+ });
557
+ else pmMarks.push({
558
+ type: markDef._type,
559
+ attrs: markDef
560
+ });
561
+ break;
562
+ }
563
+ }
564
+ return pmMarks;
565
+ }
566
+ /**
567
+ * Convert image block to ProseMirror
568
+ */
569
+ function convertImage(block) {
570
+ return {
571
+ type: "image",
572
+ attrs: {
573
+ src: block.asset.url || block.asset._ref,
574
+ alt: block.alt || "",
575
+ title: block.caption || "",
576
+ mediaId: block.asset._ref,
577
+ provider: block.asset.provider,
578
+ width: block.width,
579
+ height: block.height,
580
+ displayWidth: block.displayWidth,
581
+ displayHeight: block.displayHeight
582
+ }
583
+ };
584
+ }
585
+ /**
586
+ * Convert a malformed image block (missing `asset` wrapper) to ProseMirror.
587
+ * Handles blocks like `{ _type: "image", url: "...", alt: "..." }` that may
588
+ * originate from migrations or third-party imports.
589
+ */
590
+ function convertMalformedImage(block) {
591
+ return {
592
+ type: "image",
593
+ attrs: {
594
+ src: "url" in block && typeof block.url === "string" ? block.url : "",
595
+ alt: "alt" in block && typeof block.alt === "string" ? block.alt : "",
596
+ title: "caption" in block && typeof block.caption === "string" ? block.caption : "",
597
+ mediaId: void 0,
598
+ provider: void 0,
599
+ width: "width" in block && typeof block.width === "number" ? block.width : void 0,
600
+ height: "height" in block && typeof block.height === "number" ? block.height : void 0,
601
+ displayWidth: "displayWidth" in block && typeof block.displayWidth === "number" ? block.displayWidth : void 0,
602
+ displayHeight: "displayHeight" in block && typeof block.displayHeight === "number" ? block.displayHeight : void 0
603
+ }
604
+ };
605
+ }
606
+ /**
607
+ * Convert code block to ProseMirror
608
+ */
609
+ function convertCodeBlock(block) {
610
+ return {
611
+ type: "codeBlock",
612
+ attrs: { language: block.language || null },
613
+ content: block.code ? [{
614
+ type: "text",
615
+ text: block.code
616
+ }] : void 0
617
+ };
618
+ }
619
+
620
+ //#endregion
621
+ //#region src/after.ts
622
+ function after(fn) {
623
+ Promise.resolve().then(fn).catch((error) => {
624
+ console.error("[dineway] deferred task failed:", error);
625
+ });
626
+ }
627
+
628
+ //#endregion
629
+ //#region src/plugins/define-plugin.ts
630
+ const SIMPLE_ID = /^[a-z0-9-]+$/;
631
+ const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
632
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
633
+ function definePlugin(definition) {
634
+ if (!("id" in definition) || !("version" in definition)) {
635
+ if (!("hooks" in definition) && !("routes" in definition)) throw new Error("Standard plugin format requires at least `hooks` or `routes`. For native format, provide `id` and `version`.");
636
+ return definition;
637
+ }
638
+ return defineNativePlugin(definition);
639
+ }
640
+ /**
641
+ * Internal: define a native-format plugin with full validation and normalization.
642
+ */
643
+ function defineNativePlugin(definition) {
644
+ const { id, version, capabilities = [], allowedHosts = [], hooks = {}, routes = {}, admin = {} } = definition;
645
+ const storage = definition.storage ?? {};
646
+ if (!SIMPLE_ID.test(id) && !SCOPED_ID.test(id)) throw new Error(`Invalid plugin id "${id}". Must be lowercase alphanumeric with dashes (e.g., "my-plugin" or "@scope/my-plugin").`);
647
+ if (!SEMVER_PATTERN.test(version)) throw new Error(`Invalid plugin version "${version}". Must be semver format (e.g., "1.0.0").`);
648
+ const validCapabilities = new Set([
649
+ "network:request",
650
+ "network:request:unrestricted",
651
+ "content:read",
652
+ "content:write",
653
+ "media:read",
654
+ "media:write",
655
+ "users:read",
656
+ "email:send",
657
+ "hooks.email-transport:register",
658
+ "hooks.email-events:register",
659
+ "hooks.page-fragments:register"
660
+ ]);
661
+ for (const cap of capabilities) if (!validCapabilities.has(cap)) throw new Error(`Invalid capability "${cap}" in plugin "${id}".`);
662
+ const normalizedCapabilities = [...capabilities];
663
+ if (capabilities.includes("content:write") && !capabilities.includes("content:read")) normalizedCapabilities.push("content:read");
664
+ if (capabilities.includes("media:write") && !capabilities.includes("media:read")) normalizedCapabilities.push("media:read");
665
+ if (capabilities.includes("network:request:unrestricted") && !capabilities.includes("network:request")) normalizedCapabilities.push("network:request");
666
+ return {
667
+ id,
668
+ version,
669
+ capabilities: normalizedCapabilities,
670
+ allowedHosts,
671
+ storage,
672
+ hooks: resolveHooks(hooks, id),
673
+ routes,
674
+ admin
675
+ };
676
+ }
677
+ /**
678
+ * Resolve hooks to normalized format with defaults.
679
+ *
680
+ * PluginHooks and ResolvedPluginHooks share the same keys — each input value is
681
+ * `HookConfig<H> | H` and the output is `ResolvedHook<H>`. TS can't narrow
682
+ * the handler type through a dynamic key, so we assert at the loop boundary.
683
+ */
684
+ function resolveHooks(hooks, pluginId) {
685
+ const resolved = {};
686
+ for (const key of Object.keys(hooks)) {
687
+ const hook = hooks[key];
688
+ if (hook) resolved[key] = resolveHook(hook, pluginId);
689
+ }
690
+ return resolved;
691
+ }
692
+ /**
693
+ * Check if a hook value is a config object (has a `handler` property)
694
+ */
695
+ function isHookConfig(hook) {
696
+ return typeof hook === "object" && hook !== null && "handler" in hook;
697
+ }
698
+ /**
699
+ * Resolve a single hook to normalized format
700
+ */
701
+ function resolveHook(hook, pluginId) {
702
+ if (isHookConfig(hook)) {
703
+ if (hook.exclusive !== void 0 && typeof hook.exclusive !== "boolean") throw new Error(`Invalid "exclusive" value in hook config for plugin "${pluginId}". Must be boolean.`);
704
+ return {
705
+ priority: hook.priority ?? 100,
706
+ timeout: hook.timeout ?? 5e3,
707
+ dependencies: hook.dependencies ?? [],
708
+ errorPolicy: hook.errorPolicy ?? "abort",
709
+ exclusive: hook.exclusive ?? false,
710
+ handler: hook.handler,
711
+ pluginId
712
+ };
713
+ }
714
+ return {
715
+ priority: 100,
716
+ timeout: 5e3,
717
+ dependencies: [],
718
+ errorPolicy: "abort",
719
+ exclusive: false,
720
+ handler: hook,
721
+ pluginId
722
+ };
723
+ }
724
+
725
+ //#endregion
726
+ //#region src/plugins/hooks.ts
727
+ /**
728
+ * Plugin Hooks System v2
729
+ *
730
+ * Uses the unified PluginContext for all hooks.
731
+ * Manages lifecycle hooks with:
732
+ * - Deterministic ordering via priority + dependencies
733
+ * - Timeout enforcement
734
+ * - Error isolation
735
+ * - Observability
736
+ *
737
+ */
738
+ /**
739
+ * Hook pipeline for executing hooks in order
740
+ */
741
+ var HookPipeline = class HookPipeline {
742
+ hooks = /* @__PURE__ */ new Map();
743
+ pluginMap = /* @__PURE__ */ new Map();
744
+ contextFactory = null;
745
+ /** Stored so setContextFactory can merge incrementally. */
746
+ contextFactoryOptions = {};
747
+ /** Hook names where at least one handler declared exclusive: true */
748
+ exclusiveHookNames = /* @__PURE__ */ new Set();
749
+ /**
750
+ * Selected provider plugin ID for each exclusive hook.
751
+ * Set by the PluginManager after resolution.
752
+ */
753
+ exclusiveSelections = /* @__PURE__ */ new Map();
754
+ constructor(plugins, factoryOptions) {
755
+ if (factoryOptions) {
756
+ this.contextFactory = new PluginContextFactory(factoryOptions);
757
+ this.contextFactoryOptions = { ...factoryOptions };
758
+ }
759
+ for (const plugin of plugins) this.pluginMap.set(plugin.id, plugin);
760
+ this.registerPlugins(plugins);
761
+ }
762
+ /**
763
+ * Set or update the context factory options.
764
+ *
765
+ * When called on a pipeline that already has a factory, the new options
766
+ * are merged on top of the existing ones so that callers don't need to
767
+ * repeat every field (e.g. adding `cronReschedule` without losing
768
+ * `storage` / `getUploadUrl`).
769
+ */
770
+ setContextFactory(options) {
771
+ const merged = {
772
+ ...this.contextFactoryOptions,
773
+ ...options
774
+ };
775
+ this.contextFactory = new PluginContextFactory(merged);
776
+ this.contextFactoryOptions = merged;
777
+ }
778
+ /**
779
+ * Get context for a plugin
780
+ */
781
+ getContext(pluginId) {
782
+ const plugin = this.pluginMap.get(pluginId);
783
+ if (!plugin) throw new Error(`Plugin "${pluginId}" not found`);
784
+ if (!this.contextFactory) throw new Error("Context factory not initialized - call setContextFactory first");
785
+ return this.contextFactory.createContext(plugin);
786
+ }
787
+ /**
788
+ * Get typed hooks for a specific hook name.
789
+ * The internal map stores ResolvedHook<unknown>, but we know each name
790
+ * maps to a specific handler type via HookHandlerMap.
791
+ *
792
+ * Exclusive hooks that have a selected provider are filtered out — they
793
+ * should only run via invokeExclusiveHook(), not in the regular pipeline.
794
+ */
795
+ getTypedHooks(name) {
796
+ const all = this.hooks.get(name) ?? [];
797
+ if (this.exclusiveSelections.has(name)) return all.filter((h) => !h.exclusive);
798
+ return all;
799
+ }
800
+ /**
801
+ * Register all hooks from plugins.
802
+ *
803
+ * Registers each hook name individually to preserve type safety. The
804
+ * internal map stores ResolvedHook<unknown> since it's keyed by string,
805
+ * but getTypedHooks() restores the correct handler type on retrieval.
806
+ */
807
+ registerPlugins(plugins) {
808
+ for (const plugin of plugins) {
809
+ this.registerPluginHook(plugin, "plugin:install");
810
+ this.registerPluginHook(plugin, "plugin:activate");
811
+ this.registerPluginHook(plugin, "plugin:deactivate");
812
+ this.registerPluginHook(plugin, "plugin:uninstall");
813
+ this.registerPluginHook(plugin, "content:beforeSave");
814
+ this.registerPluginHook(plugin, "content:afterSave");
815
+ this.registerPluginHook(plugin, "content:beforeDelete");
816
+ this.registerPluginHook(plugin, "content:afterDelete");
817
+ this.registerPluginHook(plugin, "content:afterPublish");
818
+ this.registerPluginHook(plugin, "content:afterUnpublish");
819
+ this.registerPluginHook(plugin, "media:beforeUpload");
820
+ this.registerPluginHook(plugin, "media:afterUpload");
821
+ this.registerPluginHook(plugin, "cron");
822
+ this.registerPluginHook(plugin, "email:beforeSend");
823
+ this.registerPluginHook(plugin, "email:deliver");
824
+ this.registerPluginHook(plugin, "email:afterSend");
825
+ this.registerPluginHook(plugin, "comment:beforeCreate");
826
+ this.registerPluginHook(plugin, "comment:moderate");
827
+ this.registerPluginHook(plugin, "comment:afterCreate");
828
+ this.registerPluginHook(plugin, "comment:afterModerate");
829
+ this.registerPluginHook(plugin, "page:metadata");
830
+ this.registerPluginHook(plugin, "page:fragments");
831
+ }
832
+ for (const [hookName, hooks] of this.hooks) this.hooks.set(hookName, this.sortHooks(hooks));
833
+ }
834
+ /**
835
+ * Maps hook names to the capability required to register them.
836
+ *
837
+ * Hooks not listed here have no capability requirement (e.g. lifecycle
838
+ * hooks, cron). Any plugin declaring a listed hook without the required
839
+ * capability will have that hook silently skipped at registration time.
840
+ */
841
+ static HOOK_REQUIRED_CAPABILITY = new Map([
842
+ ["email:beforeSend", "hooks.email-events:register"],
843
+ ["email:afterSend", "hooks.email-events:register"],
844
+ ["email:deliver", "hooks.email-transport:register"],
845
+ ["content:beforeSave", "content:write"],
846
+ ["content:afterSave", "content:read"],
847
+ ["content:beforeDelete", "content:read"],
848
+ ["content:afterDelete", "content:read"],
849
+ ["content:afterPublish", "content:read"],
850
+ ["content:afterUnpublish", "content:read"],
851
+ ["media:beforeUpload", "media:write"],
852
+ ["media:afterUpload", "media:read"],
853
+ ["comment:beforeCreate", "users:read"],
854
+ ["comment:moderate", "users:read"],
855
+ ["comment:afterCreate", "users:read"],
856
+ ["comment:afterModerate", "users:read"],
857
+ ["page:fragments", "hooks.page-fragments:register"]
858
+ ]);
859
+ /**
860
+ * Register a single plugin's hook by name
861
+ */
862
+ registerPluginHook(plugin, name) {
863
+ const hook = plugin.hooks[name];
864
+ if (!hook) return;
865
+ const requiredCapability = HookPipeline.HOOK_REQUIRED_CAPABILITY.get(name);
866
+ if (requiredCapability && !plugin.capabilities.includes(requiredCapability)) {
867
+ console.warn(`[hooks] Plugin "${plugin.id}" declares ${name} hook without ${requiredCapability} capability — skipping`);
868
+ return;
869
+ }
870
+ if (hook.exclusive) this.exclusiveHookNames.add(name);
871
+ this.registerHook(name, hook);
872
+ }
873
+ /**
874
+ * Register a single hook
875
+ */
876
+ registerHook(name, hook) {
877
+ const existing = this.hooks.get(name) || [];
878
+ existing.push(hook);
879
+ this.hooks.set(name, existing);
880
+ }
881
+ /**
882
+ * Sort hooks by priority and dependencies
883
+ */
884
+ sortHooks(hooks) {
885
+ const sorted = [];
886
+ const remaining = [...hooks];
887
+ while (remaining.length > 0) {
888
+ const ready = remaining.filter((hook) => hook.dependencies.every((dep) => sorted.some((s) => s.pluginId === dep)));
889
+ if (ready.length === 0) {
890
+ const pluginIds = remaining.map((hook) => hook.pluginId).join(", ");
891
+ console.warn(`[hooks] Hook dependency cycle or missing dependency detected among plugins: ${pluginIds}. Falling back to priority order.`);
892
+ remaining.sort((a, b) => a.priority - b.priority);
893
+ sorted.push(...remaining);
894
+ break;
895
+ }
896
+ ready.sort((a, b) => a.priority - b.priority);
897
+ const next = ready[0];
898
+ sorted.push(next);
899
+ remaining.splice(remaining.indexOf(next), 1);
900
+ }
901
+ return sorted;
902
+ }
903
+ /**
904
+ * Execute a hook with timeout
905
+ */
906
+ async executeWithTimeout(fn, timeout) {
907
+ let timer;
908
+ const timeoutPromise = new Promise((_, reject) => {
909
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Hook timeout after ${timeout}ms`)), timeout);
910
+ });
911
+ try {
912
+ return await Promise.race([fn(), timeoutPromise]);
913
+ } finally {
914
+ if (timer) clearTimeout(timer);
915
+ }
916
+ }
917
+ /**
918
+ * Run plugin:install hooks
919
+ */
920
+ async runPluginInstall(pluginId) {
921
+ return this.runLifecycleHook("plugin:install", pluginId);
922
+ }
923
+ /**
924
+ * Run plugin:activate hooks
925
+ */
926
+ async runPluginActivate(pluginId) {
927
+ return this.runLifecycleHook("plugin:activate", pluginId);
928
+ }
929
+ /**
930
+ * Run plugin:deactivate hooks
931
+ */
932
+ async runPluginDeactivate(pluginId) {
933
+ return this.runLifecycleHook("plugin:deactivate", pluginId);
934
+ }
935
+ /**
936
+ * Run plugin:uninstall hooks
937
+ */
938
+ async runPluginUninstall(pluginId, deleteData) {
939
+ const hooks = this.getTypedHooks("plugin:uninstall");
940
+ const results = [];
941
+ const hook = hooks.find((h) => h.pluginId === pluginId);
942
+ if (!hook) return results;
943
+ const { handler } = hook;
944
+ const event = { deleteData };
945
+ const ctx = this.getContext(pluginId);
946
+ const start = Date.now();
947
+ try {
948
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
949
+ results.push({
950
+ success: true,
951
+ pluginId: hook.pluginId,
952
+ duration: Date.now() - start
953
+ });
954
+ } catch (error) {
955
+ results.push({
956
+ success: false,
957
+ error: error instanceof Error ? error : new Error(String(error)),
958
+ pluginId: hook.pluginId,
959
+ duration: Date.now() - start
960
+ });
961
+ }
962
+ return results;
963
+ }
964
+ async runLifecycleHook(hookName, pluginId) {
965
+ const hooks = this.getTypedHooks(hookName);
966
+ const results = [];
967
+ const hook = hooks.find((h) => h.pluginId === pluginId);
968
+ if (!hook) return results;
969
+ const { handler } = hook;
970
+ const event = {};
971
+ const ctx = this.getContext(pluginId);
972
+ const start = Date.now();
973
+ try {
974
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
975
+ results.push({
976
+ success: true,
977
+ pluginId: hook.pluginId,
978
+ duration: Date.now() - start
979
+ });
980
+ } catch (error) {
981
+ results.push({
982
+ success: false,
983
+ error: error instanceof Error ? error : new Error(String(error)),
984
+ pluginId: hook.pluginId,
985
+ duration: Date.now() - start
986
+ });
987
+ }
988
+ return results;
989
+ }
990
+ /**
991
+ * Run content:beforeSave hooks
992
+ * Returns modified content from the pipeline
993
+ */
994
+ async runContentBeforeSave(content, collection, isNew) {
995
+ const hooks = this.getTypedHooks("content:beforeSave");
996
+ const results = [];
997
+ let currentContent = content;
998
+ for (const hook of hooks) {
999
+ const { handler } = hook;
1000
+ const event = {
1001
+ content: currentContent,
1002
+ collection,
1003
+ isNew
1004
+ };
1005
+ const ctx = this.getContext(hook.pluginId);
1006
+ const start = Date.now();
1007
+ try {
1008
+ const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1009
+ if (result !== void 0) currentContent = result;
1010
+ results.push({
1011
+ success: true,
1012
+ value: currentContent,
1013
+ pluginId: hook.pluginId,
1014
+ duration: Date.now() - start
1015
+ });
1016
+ } catch (error) {
1017
+ results.push({
1018
+ success: false,
1019
+ error: error instanceof Error ? error : new Error(String(error)),
1020
+ pluginId: hook.pluginId,
1021
+ duration: Date.now() - start
1022
+ });
1023
+ if (hook.errorPolicy === "abort") throw error;
1024
+ }
1025
+ }
1026
+ return {
1027
+ content: currentContent,
1028
+ results
1029
+ };
1030
+ }
1031
+ /**
1032
+ * Run content:afterSave hooks
1033
+ */
1034
+ async runContentAfterSave(content, collection, isNew) {
1035
+ const hooks = this.getTypedHooks("content:afterSave");
1036
+ const results = [];
1037
+ for (const hook of hooks) {
1038
+ const { handler } = hook;
1039
+ const event = {
1040
+ content,
1041
+ collection,
1042
+ isNew
1043
+ };
1044
+ const ctx = this.getContext(hook.pluginId);
1045
+ const start = Date.now();
1046
+ try {
1047
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1048
+ results.push({
1049
+ success: true,
1050
+ pluginId: hook.pluginId,
1051
+ duration: Date.now() - start
1052
+ });
1053
+ } catch (error) {
1054
+ results.push({
1055
+ success: false,
1056
+ error: error instanceof Error ? error : new Error(String(error)),
1057
+ pluginId: hook.pluginId,
1058
+ duration: Date.now() - start
1059
+ });
1060
+ if (hook.errorPolicy === "abort") throw error;
1061
+ }
1062
+ }
1063
+ return results;
1064
+ }
1065
+ /**
1066
+ * Run content:beforeDelete hooks
1067
+ * Returns whether deletion is allowed
1068
+ */
1069
+ async runContentBeforeDelete(id, collection) {
1070
+ const hooks = this.getTypedHooks("content:beforeDelete");
1071
+ const results = [];
1072
+ let allowed = true;
1073
+ for (const hook of hooks) {
1074
+ const { handler } = hook;
1075
+ const event = {
1076
+ id,
1077
+ collection,
1078
+ permanent: false
1079
+ };
1080
+ const ctx = this.getContext(hook.pluginId);
1081
+ const start = Date.now();
1082
+ try {
1083
+ const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1084
+ if (result === false) allowed = false;
1085
+ results.push({
1086
+ success: true,
1087
+ value: result !== false,
1088
+ pluginId: hook.pluginId,
1089
+ duration: Date.now() - start
1090
+ });
1091
+ } catch (error) {
1092
+ results.push({
1093
+ success: false,
1094
+ error: error instanceof Error ? error : new Error(String(error)),
1095
+ pluginId: hook.pluginId,
1096
+ duration: Date.now() - start
1097
+ });
1098
+ if (hook.errorPolicy === "abort") throw error;
1099
+ }
1100
+ }
1101
+ return {
1102
+ allowed,
1103
+ results
1104
+ };
1105
+ }
1106
+ /**
1107
+ * Run content:afterDelete hooks
1108
+ */
1109
+ async runContentAfterDelete(id, collection, permanent) {
1110
+ const hooks = this.getTypedHooks("content:afterDelete");
1111
+ const results = [];
1112
+ for (const hook of hooks) {
1113
+ const { handler } = hook;
1114
+ const event = {
1115
+ id,
1116
+ collection,
1117
+ permanent
1118
+ };
1119
+ const ctx = this.getContext(hook.pluginId);
1120
+ const start = Date.now();
1121
+ try {
1122
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1123
+ results.push({
1124
+ success: true,
1125
+ pluginId: hook.pluginId,
1126
+ duration: Date.now() - start
1127
+ });
1128
+ } catch (error) {
1129
+ results.push({
1130
+ success: false,
1131
+ error: error instanceof Error ? error : new Error(String(error)),
1132
+ pluginId: hook.pluginId,
1133
+ duration: Date.now() - start
1134
+ });
1135
+ if (hook.errorPolicy === "abort") throw error;
1136
+ }
1137
+ }
1138
+ return results;
1139
+ }
1140
+ /**
1141
+ * Run content:afterPublish hooks (fire-and-forget).
1142
+ */
1143
+ async runContentAfterPublish(content, collection) {
1144
+ const hooks = this.getTypedHooks("content:afterPublish");
1145
+ const results = [];
1146
+ for (const hook of hooks) {
1147
+ const { handler } = hook;
1148
+ const event = {
1149
+ content,
1150
+ collection
1151
+ };
1152
+ const ctx = this.getContext(hook.pluginId);
1153
+ const start = Date.now();
1154
+ try {
1155
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1156
+ results.push({
1157
+ success: true,
1158
+ pluginId: hook.pluginId,
1159
+ duration: Date.now() - start
1160
+ });
1161
+ } catch (error) {
1162
+ results.push({
1163
+ success: false,
1164
+ error: error instanceof Error ? error : new Error(String(error)),
1165
+ pluginId: hook.pluginId,
1166
+ duration: Date.now() - start
1167
+ });
1168
+ if (hook.errorPolicy === "abort") throw error;
1169
+ }
1170
+ }
1171
+ return results;
1172
+ }
1173
+ /**
1174
+ * Run content:afterUnpublish hooks (fire-and-forget).
1175
+ */
1176
+ async runContentAfterUnpublish(content, collection) {
1177
+ const hooks = this.getTypedHooks("content:afterUnpublish");
1178
+ const results = [];
1179
+ for (const hook of hooks) {
1180
+ const { handler } = hook;
1181
+ const event = {
1182
+ content,
1183
+ collection
1184
+ };
1185
+ const ctx = this.getContext(hook.pluginId);
1186
+ const start = Date.now();
1187
+ try {
1188
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1189
+ results.push({
1190
+ success: true,
1191
+ pluginId: hook.pluginId,
1192
+ duration: Date.now() - start
1193
+ });
1194
+ } catch (error) {
1195
+ results.push({
1196
+ success: false,
1197
+ error: error instanceof Error ? error : new Error(String(error)),
1198
+ pluginId: hook.pluginId,
1199
+ duration: Date.now() - start
1200
+ });
1201
+ if (hook.errorPolicy === "abort") throw error;
1202
+ }
1203
+ }
1204
+ return results;
1205
+ }
1206
+ /**
1207
+ * Run media:beforeUpload hooks
1208
+ */
1209
+ async runMediaBeforeUpload(file) {
1210
+ const hooks = this.getTypedHooks("media:beforeUpload");
1211
+ const results = [];
1212
+ let currentFile = file;
1213
+ for (const hook of hooks) {
1214
+ const { handler } = hook;
1215
+ const event = { file: currentFile };
1216
+ const ctx = this.getContext(hook.pluginId);
1217
+ const start = Date.now();
1218
+ try {
1219
+ const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1220
+ if (result !== void 0) currentFile = result;
1221
+ results.push({
1222
+ success: true,
1223
+ value: currentFile,
1224
+ pluginId: hook.pluginId,
1225
+ duration: Date.now() - start
1226
+ });
1227
+ } catch (error) {
1228
+ results.push({
1229
+ success: false,
1230
+ error: error instanceof Error ? error : new Error(String(error)),
1231
+ pluginId: hook.pluginId,
1232
+ duration: Date.now() - start
1233
+ });
1234
+ if (hook.errorPolicy === "abort") throw error;
1235
+ }
1236
+ }
1237
+ return {
1238
+ file: currentFile,
1239
+ results
1240
+ };
1241
+ }
1242
+ /**
1243
+ * Run media:afterUpload hooks
1244
+ */
1245
+ async runMediaAfterUpload(media) {
1246
+ const hooks = this.getTypedHooks("media:afterUpload");
1247
+ const results = [];
1248
+ for (const hook of hooks) {
1249
+ const { handler } = hook;
1250
+ const event = { media };
1251
+ const ctx = this.getContext(hook.pluginId);
1252
+ const start = Date.now();
1253
+ try {
1254
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1255
+ results.push({
1256
+ success: true,
1257
+ pluginId: hook.pluginId,
1258
+ duration: Date.now() - start
1259
+ });
1260
+ } catch (error) {
1261
+ results.push({
1262
+ success: false,
1263
+ error: error instanceof Error ? error : new Error(String(error)),
1264
+ pluginId: hook.pluginId,
1265
+ duration: Date.now() - start
1266
+ });
1267
+ if (hook.errorPolicy === "abort") throw error;
1268
+ }
1269
+ }
1270
+ return results;
1271
+ }
1272
+ /**
1273
+ * Invoke the cron hook for a specific plugin.
1274
+ *
1275
+ * Unlike other hooks which broadcast to all plugins, the cron hook is
1276
+ * dispatched only to the target plugin — the one that owns the task.
1277
+ */
1278
+ async invokeCronHook(pluginId, event) {
1279
+ const hook = this.getTypedHooks("cron").find((h) => h.pluginId === pluginId);
1280
+ if (!hook) return {
1281
+ success: false,
1282
+ error: /* @__PURE__ */ new Error(`Plugin "${pluginId}" has no cron hook registered`),
1283
+ pluginId,
1284
+ duration: 0
1285
+ };
1286
+ const { handler } = hook;
1287
+ const ctx = this.getContext(pluginId);
1288
+ const start = Date.now();
1289
+ try {
1290
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1291
+ return {
1292
+ success: true,
1293
+ pluginId,
1294
+ duration: Date.now() - start
1295
+ };
1296
+ } catch (error) {
1297
+ return {
1298
+ success: false,
1299
+ error: error instanceof Error ? error : new Error(String(error)),
1300
+ pluginId,
1301
+ duration: Date.now() - start
1302
+ };
1303
+ }
1304
+ }
1305
+ /**
1306
+ * Run email:beforeSend hooks (middleware pipeline).
1307
+ *
1308
+ * Each handler receives the message and returns a modified message or
1309
+ * `false` to cancel delivery. The pipeline chains message transformations —
1310
+ * each handler receives the output of the previous one.
1311
+ */
1312
+ async runEmailBeforeSend(message, source) {
1313
+ const hooks = this.getTypedHooks("email:beforeSend");
1314
+ const results = [];
1315
+ let currentMessage = message;
1316
+ for (const hook of hooks) {
1317
+ const { handler } = hook;
1318
+ const event = {
1319
+ message: { ...currentMessage },
1320
+ source
1321
+ };
1322
+ const ctx = this.getContext(hook.pluginId);
1323
+ const start = Date.now();
1324
+ try {
1325
+ const result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1326
+ if (result === false) {
1327
+ results.push({
1328
+ success: true,
1329
+ value: false,
1330
+ pluginId: hook.pluginId,
1331
+ duration: Date.now() - start
1332
+ });
1333
+ return {
1334
+ message: false,
1335
+ results
1336
+ };
1337
+ }
1338
+ if (result && typeof result === "object") currentMessage = result;
1339
+ results.push({
1340
+ success: true,
1341
+ value: currentMessage,
1342
+ pluginId: hook.pluginId,
1343
+ duration: Date.now() - start
1344
+ });
1345
+ } catch (error) {
1346
+ results.push({
1347
+ success: false,
1348
+ error: error instanceof Error ? error : new Error(String(error)),
1349
+ pluginId: hook.pluginId,
1350
+ duration: Date.now() - start
1351
+ });
1352
+ if (hook.errorPolicy === "abort") throw error;
1353
+ }
1354
+ }
1355
+ return {
1356
+ message: currentMessage,
1357
+ results
1358
+ };
1359
+ }
1360
+ /**
1361
+ * Run email:afterSend hooks (fire-and-forget).
1362
+ *
1363
+ * Errors are logged but don't propagate — they don't affect the caller.
1364
+ */
1365
+ async runEmailAfterSend(message, source) {
1366
+ const hooks = this.getTypedHooks("email:afterSend");
1367
+ const results = [];
1368
+ for (const hook of hooks) {
1369
+ const { handler } = hook;
1370
+ const event = {
1371
+ message,
1372
+ source
1373
+ };
1374
+ const ctx = this.getContext(hook.pluginId);
1375
+ const start = Date.now();
1376
+ try {
1377
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1378
+ results.push({
1379
+ success: true,
1380
+ pluginId: hook.pluginId,
1381
+ duration: Date.now() - start
1382
+ });
1383
+ } catch (error) {
1384
+ console.error(`[email:afterSend] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
1385
+ results.push({
1386
+ success: false,
1387
+ error: error instanceof Error ? error : new Error(String(error)),
1388
+ pluginId: hook.pluginId,
1389
+ duration: Date.now() - start
1390
+ });
1391
+ }
1392
+ }
1393
+ return results;
1394
+ }
1395
+ /**
1396
+ * Run comment:beforeCreate hooks (middleware pipeline).
1397
+ *
1398
+ * Each handler receives the event and returns a modified event or
1399
+ * `false` to reject the comment. The pipeline chains transformations —
1400
+ * each handler receives the output of the previous one.
1401
+ */
1402
+ async runCommentBeforeCreate(event) {
1403
+ const hooks = this.getTypedHooks("comment:beforeCreate");
1404
+ let currentEvent = event;
1405
+ for (const hook of hooks) {
1406
+ const { handler } = hook;
1407
+ const ctx = this.getContext(hook.pluginId);
1408
+ const start = Date.now();
1409
+ try {
1410
+ const result = await this.executeWithTimeout(() => handler({ ...currentEvent }, ctx), hook.timeout);
1411
+ if (result === false) return false;
1412
+ if (result && typeof result === "object") currentEvent = result;
1413
+ } catch (error) {
1414
+ console.error(`[comment:beforeCreate] Plugin "${hook.pluginId}" error (${Date.now() - start}ms):`, error instanceof Error ? error.message : error);
1415
+ if (hook.errorPolicy === "abort") throw error;
1416
+ }
1417
+ }
1418
+ return currentEvent;
1419
+ }
1420
+ /**
1421
+ * Run comment:afterCreate hooks (fire-and-forget).
1422
+ *
1423
+ * Errors are logged but don't propagate — they don't affect the caller.
1424
+ */
1425
+ async runCommentAfterCreate(event) {
1426
+ const hooks = this.getTypedHooks("comment:afterCreate");
1427
+ for (const hook of hooks) {
1428
+ const { handler } = hook;
1429
+ const ctx = this.getContext(hook.pluginId);
1430
+ try {
1431
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1432
+ } catch (error) {
1433
+ console.error(`[comment:afterCreate] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
1434
+ }
1435
+ }
1436
+ }
1437
+ /**
1438
+ * Run comment:afterModerate hooks (fire-and-forget).
1439
+ *
1440
+ * Errors are logged but don't propagate — they don't affect the caller.
1441
+ */
1442
+ async runCommentAfterModerate(event) {
1443
+ const hooks = this.getTypedHooks("comment:afterModerate");
1444
+ for (const hook of hooks) {
1445
+ const { handler } = hook;
1446
+ const ctx = this.getContext(hook.pluginId);
1447
+ try {
1448
+ await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
1449
+ } catch (error) {
1450
+ console.error(`[comment:afterModerate] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
1451
+ }
1452
+ }
1453
+ }
1454
+ /**
1455
+ * Run page:metadata hooks. Each handler returns contributions that are
1456
+ * merged by the metadata collector. Errors are logged but don't propagate.
1457
+ */
1458
+ async runPageMetadata(event) {
1459
+ const hooks = this.getTypedHooks("page:metadata");
1460
+ const results = [];
1461
+ for (const hook of hooks) {
1462
+ const { handler } = hook;
1463
+ const ctx = this.getContext(hook.pluginId);
1464
+ try {
1465
+ const result = await this.executeWithTimeout(() => Promise.resolve(handler(event, ctx)), hook.timeout);
1466
+ if (result != null) {
1467
+ const contributions = Array.isArray(result) ? result : [result];
1468
+ results.push({
1469
+ pluginId: hook.pluginId,
1470
+ contributions
1471
+ });
1472
+ }
1473
+ } catch (error) {
1474
+ console.error(`[page:metadata] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
1475
+ }
1476
+ }
1477
+ return results;
1478
+ }
1479
+ /**
1480
+ * Run page:fragments hooks. Only trusted plugins should be registered
1481
+ * for this hook. Errors are logged but don't propagate.
1482
+ */
1483
+ async runPageFragments(event) {
1484
+ const hooks = this.getTypedHooks("page:fragments");
1485
+ const results = [];
1486
+ for (const hook of hooks) {
1487
+ const { handler } = hook;
1488
+ const ctx = this.getContext(hook.pluginId);
1489
+ try {
1490
+ const result = await this.executeWithTimeout(() => Promise.resolve(handler(event, ctx)), hook.timeout);
1491
+ if (result != null) {
1492
+ const contributions = Array.isArray(result) ? result : [result];
1493
+ results.push({
1494
+ pluginId: hook.pluginId,
1495
+ contributions
1496
+ });
1497
+ }
1498
+ } catch (error) {
1499
+ console.error(`[page:fragments] Plugin "${hook.pluginId}" error:`, error instanceof Error ? error.message : error);
1500
+ }
1501
+ }
1502
+ return results;
1503
+ }
1504
+ /**
1505
+ * Check if any hooks are registered for a given name
1506
+ */
1507
+ hasHooks(name) {
1508
+ const hooks = this.hooks.get(name);
1509
+ return hooks !== void 0 && hooks.length > 0;
1510
+ }
1511
+ /**
1512
+ * Get hook count for debugging
1513
+ */
1514
+ getHookCount(name) {
1515
+ return this.hooks.get(name)?.length || 0;
1516
+ }
1517
+ /**
1518
+ * Get all registered hook names
1519
+ */
1520
+ getRegisteredHooks() {
1521
+ return [...this.hooks.keys()];
1522
+ }
1523
+ /**
1524
+ * Returns hook names where at least one handler declared exclusive: true
1525
+ */
1526
+ getRegisteredExclusiveHooks() {
1527
+ return [...this.exclusiveHookNames];
1528
+ }
1529
+ /**
1530
+ * Check if a hook is exclusive
1531
+ */
1532
+ isExclusiveHook(name) {
1533
+ return this.exclusiveHookNames.has(name);
1534
+ }
1535
+ /**
1536
+ * Set the selected provider for an exclusive hook.
1537
+ * Called by PluginManager after resolution.
1538
+ */
1539
+ setExclusiveSelection(hookName, pluginId) {
1540
+ this.exclusiveSelections.set(hookName, pluginId);
1541
+ }
1542
+ /**
1543
+ * Clear the selected provider for an exclusive hook.
1544
+ */
1545
+ clearExclusiveSelection(hookName) {
1546
+ this.exclusiveSelections.delete(hookName);
1547
+ }
1548
+ /**
1549
+ * Get the selected provider for an exclusive hook (if any).
1550
+ */
1551
+ getExclusiveSelection(hookName) {
1552
+ return this.exclusiveSelections.get(hookName);
1553
+ }
1554
+ /**
1555
+ * Get all plugins that registered a handler for a given exclusive hook.
1556
+ */
1557
+ getExclusiveHookProviders(hookName) {
1558
+ return (this.hooks.get(hookName) ?? []).filter((h) => h.exclusive).map((h) => ({ pluginId: h.pluginId }));
1559
+ }
1560
+ /**
1561
+ * Get all plugins that registered a non-exclusive handler for a given hook,
1562
+ * preserving priority order. This partitions with getExclusiveHookProviders().
1563
+ */
1564
+ getHookProviders(hookName) {
1565
+ return (this.hooks.get(hookName) ?? []).filter((h) => !h.exclusive).map((h) => ({ pluginId: h.pluginId }));
1566
+ }
1567
+ /**
1568
+ * Invoke an exclusive hook — dispatch only to the selected provider.
1569
+ * Returns null if no provider is selected or if the selected hook
1570
+ * is not found in the pipeline.
1571
+ *
1572
+ * This is a generic dispatch used by the email pipeline and other
1573
+ * exclusive hook consumers. The handler type is unknown — callers
1574
+ * must know the expected signature.
1575
+ *
1576
+ * Errors are isolated: a failing handler returns an error result
1577
+ * instead of propagating the exception to the caller.
1578
+ */
1579
+ async invokeExclusiveHook(hookName, event) {
1580
+ const selectedPluginId = this.exclusiveSelections.get(hookName);
1581
+ if (!selectedPluginId) return null;
1582
+ const hook = (this.hooks.get(hookName) ?? []).find((h) => h.pluginId === selectedPluginId && h.exclusive);
1583
+ if (!hook) return null;
1584
+ const start = Date.now();
1585
+ try {
1586
+ const ctx = this.getContext(selectedPluginId);
1587
+ const handler = hook.handler;
1588
+ return {
1589
+ result: await this.executeWithTimeout(() => handler(event, ctx), hook.timeout),
1590
+ pluginId: selectedPluginId,
1591
+ duration: Date.now() - start
1592
+ };
1593
+ } catch (error) {
1594
+ return {
1595
+ result: void 0,
1596
+ pluginId: selectedPluginId,
1597
+ error: error instanceof Error ? error : new Error(String(error)),
1598
+ duration: Date.now() - start
1599
+ };
1600
+ }
1601
+ }
1602
+ };
1603
+ /**
1604
+ * Create a hook pipeline from plugins
1605
+ */
1606
+ function createHookPipeline(plugins, factoryOptions) {
1607
+ return new HookPipeline(plugins, factoryOptions);
1608
+ }
1609
+ /** Options table key prefix for exclusive hook selections */
1610
+ const EXCLUSIVE_HOOK_KEY_PREFIX$1 = "dineway:exclusive_hook:";
1611
+ /**
1612
+ * Resolve exclusive hook selections.
1613
+ *
1614
+ * Shared algorithm used by both PluginManager and DinewayRuntime:
1615
+ * 1. If a DB selection exists and that plugin is active → keep it.
1616
+ * 2. If DB selection is stale (plugin inactive/gone) → clear it.
1617
+ * 3. If no selection and only one active provider → auto-select it.
1618
+ * 4. If preferred hints match an active provider → first match wins.
1619
+ * 5. If multiple providers and no hint → leave unselected (admin must choose).
1620
+ */
1621
+ async function resolveExclusiveHooks(opts) {
1622
+ const { pipeline, isActive, getOption, setOption, deleteOption, preferredHints } = opts;
1623
+ const exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();
1624
+ for (const hookName of exclusiveHookNames) {
1625
+ const providers = pipeline.getExclusiveHookProviders(hookName);
1626
+ const activeProviderIds = new Set(providers.map((p) => p.pluginId).filter((id) => isActive(id)));
1627
+ const key = `${EXCLUSIVE_HOOK_KEY_PREFIX$1}${hookName}`;
1628
+ let currentSelection = null;
1629
+ try {
1630
+ currentSelection = await getOption(key);
1631
+ } catch {
1632
+ continue;
1633
+ }
1634
+ if (currentSelection && activeProviderIds.has(currentSelection)) {
1635
+ pipeline.setExclusiveSelection(hookName, currentSelection);
1636
+ continue;
1637
+ }
1638
+ if (currentSelection) try {
1639
+ await deleteOption(key);
1640
+ } catch {}
1641
+ if (activeProviderIds.size === 1) {
1642
+ const [onlyProvider] = activeProviderIds;
1643
+ try {
1644
+ await setOption(key, onlyProvider);
1645
+ } catch {}
1646
+ pipeline.setExclusiveSelection(hookName, onlyProvider);
1647
+ continue;
1648
+ }
1649
+ if (preferredHints) {
1650
+ let found = false;
1651
+ for (const [pluginId, hooks] of preferredHints) if (hooks.includes(hookName) && activeProviderIds.has(pluginId)) {
1652
+ try {
1653
+ await setOption(key, pluginId);
1654
+ } catch {}
1655
+ pipeline.setExclusiveSelection(hookName, pluginId);
1656
+ found = true;
1657
+ break;
1658
+ }
1659
+ if (found) continue;
1660
+ }
1661
+ pipeline.clearExclusiveSelection(hookName);
1662
+ }
1663
+ }
1664
+
1665
+ //#endregion
1666
+ //#region src/plugins/email.ts
1667
+ /**
1668
+ * Email Pipeline
1669
+ *
1670
+ * Orchestrates the three-stage email pipeline:
1671
+ * 1. email:beforeSend hooks (middleware — transform, validate, cancel)
1672
+ * 2. email:deliver hook (exclusive — exactly one provider delivers)
1673
+ * 3. email:afterSend hooks (logging, analytics, fire-and-forget)
1674
+ *
1675
+ * Security features:
1676
+ * - Recursion guard prevents re-entrant sends (e.g. plugin calling ctx.email.send from a hook)
1677
+ * - System emails (source="system") bypass email:beforeSend and email:afterSend hooks entirely
1678
+ * to protect auth tokens from exfiltration by plugin hooks
1679
+ *
1680
+ */
1681
+ /** Hook name for the exclusive email delivery hook */
1682
+ const EMAIL_DELIVER_HOOK = "email:deliver";
1683
+ /** Source value used for auth emails (magic links, invites, password resets) */
1684
+ const SYSTEM_SOURCE = "system";
1685
+ /**
1686
+ * Error thrown when ctx.email.send() is called but no provider is configured.
1687
+ */
1688
+ var EmailNotConfiguredError = class extends Error {
1689
+ constructor() {
1690
+ super("No email provider is configured. Install and activate an email provider plugin, then select it in Settings > Email.");
1691
+ this.name = "EmailNotConfiguredError";
1692
+ }
1693
+ };
1694
+ /**
1695
+ * Error thrown when a recursive email send is detected.
1696
+ */
1697
+ var EmailRecursionError = class extends Error {
1698
+ constructor() {
1699
+ super("Recursive email send detected. A plugin hook attempted to send an email from within the email pipeline, which would cause infinite recursion.");
1700
+ this.name = "EmailRecursionError";
1701
+ }
1702
+ };
1703
+ /**
1704
+ * Recursion guard using AsyncLocalStorage.
1705
+ *
1706
+ * EmailPipeline is a singleton (worker-lifetime cached via DinewayRuntime).
1707
+ * Instance state like `sendDepth` would false-positive under concurrent
1708
+ * requests because two unrelated sends would increment the same counter.
1709
+ * ALS scopes the guard to the current async execution context, so concurrent
1710
+ * requests each get their own independent recursion tracking.
1711
+ */
1712
+ const emailSendALS = new AsyncLocalStorage();
1713
+ /**
1714
+ * EmailPipeline orchestrates email delivery through the plugin hook system.
1715
+ *
1716
+ * The pipeline runs in three stages:
1717
+ * 1. email:beforeSend — middleware hooks that can transform or cancel messages
1718
+ * 2. email:deliver — exclusive hook dispatching to the selected provider
1719
+ * 3. email:afterSend — fire-and-forget hooks for logging/analytics
1720
+ */
1721
+ var EmailPipeline = class {
1722
+ pipeline;
1723
+ constructor(pipeline) {
1724
+ this.pipeline = pipeline;
1725
+ }
1726
+ /**
1727
+ * Replace the underlying hook pipeline.
1728
+ *
1729
+ * Called by the runtime when rebuilding the hook pipeline after a
1730
+ * plugin is enabled or disabled, so the email pipeline dispatches
1731
+ * to the current set of active hooks.
1732
+ */
1733
+ setPipeline(pipeline) {
1734
+ this.pipeline = pipeline;
1735
+ }
1736
+ /**
1737
+ * Send an email through the full pipeline.
1738
+ *
1739
+ * @param message - The email to send
1740
+ * @param source - Where the email originated ("system" for auth, plugin ID for plugins)
1741
+ * @throws EmailNotConfiguredError if no provider is selected
1742
+ * @throws EmailRecursionError if called re-entrantly from within a hook
1743
+ * @throws Error if the provider handler throws
1744
+ */
1745
+ async send(message, source) {
1746
+ const store = emailSendALS.getStore();
1747
+ if (store && store.depth > 0) throw new EmailRecursionError();
1748
+ const run = () => this.sendInner(message, source);
1749
+ if (store) {
1750
+ store.depth++;
1751
+ try {
1752
+ await run();
1753
+ } finally {
1754
+ store.depth--;
1755
+ }
1756
+ } else await emailSendALS.run({ depth: 1 }, run);
1757
+ }
1758
+ /**
1759
+ * Inner send implementation, separated from the recursion guard.
1760
+ */
1761
+ async sendInner(message, source) {
1762
+ if (!message || typeof message !== "object") throw new Error("Invalid email message: message must be an object");
1763
+ if (!message.to || typeof message.to !== "string") throw new Error("Invalid email message: 'to' is required and must be a string");
1764
+ if (!message.subject || typeof message.subject !== "string") throw new Error("Invalid email message: 'subject' is required and must be a string");
1765
+ if (!message.text || typeof message.text !== "string") throw new Error("Invalid email message: 'text' is required and must be a string");
1766
+ const isSystemEmail = source === SYSTEM_SOURCE;
1767
+ let finalMessage;
1768
+ if (isSystemEmail) finalMessage = message;
1769
+ else {
1770
+ const beforeResult = await this.pipeline.runEmailBeforeSend(message, source);
1771
+ if (beforeResult.message === false) {
1772
+ const cancelledBy = beforeResult.results.find((r) => r.value === false)?.pluginId ?? "unknown";
1773
+ console.info(`[email] Email to "${message.to}" cancelled by plugin "${cancelledBy}"`);
1774
+ return;
1775
+ }
1776
+ finalMessage = beforeResult.message;
1777
+ }
1778
+ const deliverEvent = {
1779
+ message: finalMessage,
1780
+ source
1781
+ };
1782
+ const deliverResult = await this.pipeline.invokeExclusiveHook(EMAIL_DELIVER_HOOK, deliverEvent);
1783
+ if (!deliverResult) throw new EmailNotConfiguredError();
1784
+ if (deliverResult.error) throw deliverResult.error;
1785
+ if (!isSystemEmail) this.pipeline.runEmailAfterSend(finalMessage, source).catch((err) => console.error("[email] afterSend pipeline error:", err instanceof Error ? err.message : err));
1786
+ }
1787
+ /**
1788
+ * Check if an email provider is configured and available.
1789
+ *
1790
+ * Returns true if an email:deliver provider is selected in the exclusive
1791
+ * hook system. Plugins and auth code use this to decide whether to show
1792
+ * "send invite" vs "copy invite link" UI.
1793
+ */
1794
+ isAvailable() {
1795
+ return this.pipeline.getExclusiveSelection(EMAIL_DELIVER_HOOK) !== void 0;
1796
+ }
1797
+ };
1798
+
1799
+ //#endregion
1800
+ //#region src/plugins/routes.ts
1801
+ /**
1802
+ * Plugin Routes v2
1803
+ *
1804
+ * Handles plugin API route invocation with:
1805
+ * - Input validation via Zod schemas
1806
+ * - Route context creation
1807
+ * - Error handling
1808
+ *
1809
+ */
1810
+ /**
1811
+ * Route handler for a plugin
1812
+ */
1813
+ var PluginRouteHandler = class {
1814
+ contextFactory;
1815
+ plugin;
1816
+ trustedProxyHeaders;
1817
+ constructor(plugin, factoryOptions) {
1818
+ this.plugin = plugin;
1819
+ this.contextFactory = new PluginContextFactory(factoryOptions);
1820
+ this.trustedProxyHeaders = factoryOptions.trustedProxyHeaders ?? [];
1821
+ }
1822
+ /**
1823
+ * Invoke a route by name
1824
+ */
1825
+ async invoke(routeName, options) {
1826
+ const route = this.plugin.routes[routeName];
1827
+ if (!route) return {
1828
+ success: false,
1829
+ error: {
1830
+ code: "ROUTE_NOT_FOUND",
1831
+ message: `Route "${routeName}" not found in plugin "${this.plugin.id}"`
1832
+ },
1833
+ status: 404
1834
+ };
1835
+ let validatedInput;
1836
+ if (route.input) {
1837
+ const parseResult = route.input.safeParse(options.body);
1838
+ if (!parseResult.success) return {
1839
+ success: false,
1840
+ error: {
1841
+ code: "VALIDATION_ERROR",
1842
+ message: "Invalid request body",
1843
+ details: parseResult.error.format()
1844
+ },
1845
+ status: 400
1846
+ };
1847
+ validatedInput = parseResult.data;
1848
+ } else validatedInput = options.body;
1849
+ const routeContext = {
1850
+ ...this.contextFactory.createContext(this.plugin),
1851
+ input: validatedInput,
1852
+ request: options.request,
1853
+ requestMeta: extractRequestMeta(options.request, this.trustedProxyHeaders)
1854
+ };
1855
+ try {
1856
+ return {
1857
+ success: true,
1858
+ data: await route.handler(routeContext),
1859
+ status: 200
1860
+ };
1861
+ } catch (error) {
1862
+ if (error instanceof PluginRouteError) return {
1863
+ success: false,
1864
+ error: {
1865
+ code: error.code,
1866
+ message: error.message,
1867
+ details: error.details
1868
+ },
1869
+ status: error.status
1870
+ };
1871
+ console.error(`[plugin:${this.plugin.id}] Route handler failed:`, error);
1872
+ return {
1873
+ success: false,
1874
+ error: {
1875
+ code: "INTERNAL_ERROR",
1876
+ message: "An internal error occurred"
1877
+ },
1878
+ status: 500
1879
+ };
1880
+ }
1881
+ }
1882
+ /**
1883
+ * Get all route names
1884
+ */
1885
+ getRouteNames() {
1886
+ return Object.keys(this.plugin.routes);
1887
+ }
1888
+ /**
1889
+ * Check if a route exists
1890
+ */
1891
+ hasRoute(name) {
1892
+ return name in this.plugin.routes;
1893
+ }
1894
+ /**
1895
+ * Get route metadata without invoking the handler.
1896
+ * Returns null if the route doesn't exist.
1897
+ */
1898
+ getRouteMeta(name) {
1899
+ const route = this.plugin.routes[name];
1900
+ if (!route) return null;
1901
+ return { public: route.public === true };
1902
+ }
1903
+ };
1904
+ /**
1905
+ * Error class for plugin routes
1906
+ * Allows plugins to return structured errors with specific HTTP status codes
1907
+ */
1908
+ var PluginRouteError = class PluginRouteError extends Error {
1909
+ constructor(code, message, status = 400, details) {
1910
+ super(message);
1911
+ this.code = code;
1912
+ this.status = status;
1913
+ this.details = details;
1914
+ this.name = "PluginRouteError";
1915
+ }
1916
+ /**
1917
+ * Create a bad request error (400)
1918
+ */
1919
+ static badRequest(message, details) {
1920
+ return new PluginRouteError("BAD_REQUEST", message, 400, details);
1921
+ }
1922
+ /**
1923
+ * Create an unauthorized error (401)
1924
+ */
1925
+ static unauthorized(message = "Unauthorized") {
1926
+ return new PluginRouteError("UNAUTHORIZED", message, 401);
1927
+ }
1928
+ /**
1929
+ * Create a forbidden error (403)
1930
+ */
1931
+ static forbidden(message = "Forbidden") {
1932
+ return new PluginRouteError("FORBIDDEN", message, 403);
1933
+ }
1934
+ /**
1935
+ * Create a not found error (404)
1936
+ */
1937
+ static notFound(message = "Not found") {
1938
+ return new PluginRouteError("NOT_FOUND", message, 404);
1939
+ }
1940
+ /**
1941
+ * Create a conflict error (409)
1942
+ */
1943
+ static conflict(message, details) {
1944
+ return new PluginRouteError("CONFLICT", message, 409, details);
1945
+ }
1946
+ /**
1947
+ * Create an internal error (500)
1948
+ */
1949
+ static internal(message = "Internal error") {
1950
+ return new PluginRouteError("INTERNAL_ERROR", message, 500);
1951
+ }
1952
+ };
1953
+ /**
1954
+ * Registry for all plugin route handlers
1955
+ */
1956
+ var PluginRouteRegistry = class {
1957
+ handlers = /* @__PURE__ */ new Map();
1958
+ constructor(factoryOptions) {
1959
+ this.factoryOptions = factoryOptions;
1960
+ }
1961
+ /**
1962
+ * Register a plugin's routes
1963
+ */
1964
+ register(plugin) {
1965
+ const handler = new PluginRouteHandler(plugin, this.factoryOptions);
1966
+ this.handlers.set(plugin.id, handler);
1967
+ }
1968
+ /**
1969
+ * Unregister a plugin's routes
1970
+ */
1971
+ unregister(pluginId) {
1972
+ this.handlers.delete(pluginId);
1973
+ }
1974
+ /**
1975
+ * Invoke a plugin route
1976
+ */
1977
+ async invoke(pluginId, routeName, options) {
1978
+ const handler = this.handlers.get(pluginId);
1979
+ if (!handler) return {
1980
+ success: false,
1981
+ error: {
1982
+ code: "PLUGIN_NOT_FOUND",
1983
+ message: `Plugin "${pluginId}" not found`
1984
+ },
1985
+ status: 404
1986
+ };
1987
+ return handler.invoke(routeName, options);
1988
+ }
1989
+ /**
1990
+ * Get all registered plugin IDs
1991
+ */
1992
+ getPluginIds() {
1993
+ return [...this.handlers.keys()];
1994
+ }
1995
+ /**
1996
+ * Get routes for a plugin
1997
+ */
1998
+ getRoutes(pluginId) {
1999
+ return this.handlers.get(pluginId)?.getRouteNames() ?? [];
2000
+ }
2001
+ /**
2002
+ * Get route metadata for a specific plugin route.
2003
+ * Returns null if the plugin or route doesn't exist.
2004
+ */
2005
+ getRouteMeta(pluginId, routeName) {
2006
+ const handler = this.handlers.get(pluginId);
2007
+ if (!handler) return null;
2008
+ return handler.getRouteMeta(routeName);
2009
+ }
2010
+ };
2011
+
2012
+ //#endregion
2013
+ //#region src/plugins/manager.ts
2014
+ /** Options table key prefix for exclusive hook DB reads via PluginManager */
2015
+ const EXCLUSIVE_HOOK_KEY_PREFIX = "dineway:exclusive_hook:";
2016
+ /**
2017
+ * Plugin Manager v2
2018
+ *
2019
+ * Manages the full lifecycle of plugins and coordinates hooks/routes.
2020
+ */
2021
+ var PluginManager = class {
2022
+ plugins = /* @__PURE__ */ new Map();
2023
+ hookPipeline = null;
2024
+ routeRegistry = null;
2025
+ factoryOptions;
2026
+ initialized = false;
2027
+ constructor(options) {
2028
+ this.options = options;
2029
+ this.factoryOptions = {
2030
+ db: options.db,
2031
+ storage: options.storage,
2032
+ getUploadUrl: options.getUploadUrl,
2033
+ trustedProxyHeaders: options.trustedProxyHeaders
2034
+ };
2035
+ }
2036
+ /**
2037
+ * Set the email pipeline used when creating plugin contexts.
2038
+ * Reinitializes routes/hooks if already initialized so ctx.email is available immediately.
2039
+ */
2040
+ setEmailPipeline(pipeline) {
2041
+ this.factoryOptions.emailPipeline = pipeline;
2042
+ if (this.initialized) this.reinitialize();
2043
+ }
2044
+ /**
2045
+ * Register a plugin definition
2046
+ * This resolves the definition and adds it to the manager, but doesn't install it
2047
+ */
2048
+ register(definition) {
2049
+ const resolved = definePlugin(definition);
2050
+ if (this.plugins.has(resolved.id)) throw new Error(`Plugin "${resolved.id}" is already registered`);
2051
+ this.plugins.set(resolved.id, {
2052
+ plugin: resolved,
2053
+ state: "registered"
2054
+ });
2055
+ this.initialized = false;
2056
+ return resolved;
2057
+ }
2058
+ /**
2059
+ * Register multiple plugins
2060
+ */
2061
+ registerAll(definitions) {
2062
+ for (const def of definitions) this.register(def);
2063
+ }
2064
+ /**
2065
+ * Unregister a plugin
2066
+ * Plugin must be inactive or just registered
2067
+ */
2068
+ unregister(pluginId) {
2069
+ const entry = this.plugins.get(pluginId);
2070
+ if (!entry) return false;
2071
+ if (entry.state === "active") throw new Error(`Cannot unregister active plugin "${pluginId}". Deactivate it first.`);
2072
+ this.plugins.delete(pluginId);
2073
+ this.initialized = false;
2074
+ return true;
2075
+ }
2076
+ /**
2077
+ * Install a plugin (run install hooks, set up storage)
2078
+ */
2079
+ async install(pluginId) {
2080
+ const entry = this.plugins.get(pluginId);
2081
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2082
+ if (entry.state !== "registered") throw new Error(`Plugin "${pluginId}" is already installed (state: ${entry.state})`);
2083
+ this.ensureInitialized();
2084
+ const results = await this.hookPipeline.runPluginInstall(pluginId);
2085
+ const failed = results.find((r) => !r.success);
2086
+ if (failed) throw new Error(`Plugin install failed: ${failed.error?.message ?? "Unknown error"}`);
2087
+ entry.state = "installed";
2088
+ return results;
2089
+ }
2090
+ /**
2091
+ * Activate a plugin (run activate hooks, enable hooks/routes)
2092
+ */
2093
+ async activate(pluginId) {
2094
+ const entry = this.plugins.get(pluginId);
2095
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2096
+ if (entry.state === "active") return [];
2097
+ if (entry.state === "registered") await this.install(pluginId);
2098
+ this.ensureInitialized();
2099
+ const results = await this.hookPipeline.runPluginActivate(pluginId);
2100
+ const failed = results.find((r) => !r.success);
2101
+ if (failed) throw new Error(`Plugin activation failed: ${failed.error?.message ?? "Unknown error"}`);
2102
+ entry.state = "active";
2103
+ await setCronTasksEnabled(this.options.db, pluginId, true);
2104
+ this.reinitialize();
2105
+ await this.resolveExclusiveHooks();
2106
+ return results;
2107
+ }
2108
+ /**
2109
+ * Deactivate a plugin (run deactivate hooks, disable hooks/routes)
2110
+ */
2111
+ async deactivate(pluginId) {
2112
+ const entry = this.plugins.get(pluginId);
2113
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2114
+ if (entry.state !== "active") return [];
2115
+ this.ensureInitialized();
2116
+ const results = await this.hookPipeline.runPluginDeactivate(pluginId);
2117
+ await setCronTasksEnabled(this.options.db, pluginId, false);
2118
+ entry.state = "inactive";
2119
+ this.reinitialize();
2120
+ await this.resolveExclusiveHooks();
2121
+ return results;
2122
+ }
2123
+ /**
2124
+ * Uninstall a plugin (run uninstall hooks, optionally delete data)
2125
+ */
2126
+ async uninstall(pluginId, deleteData = false) {
2127
+ const entry = this.plugins.get(pluginId);
2128
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2129
+ if (entry.state === "active") await this.deactivate(pluginId);
2130
+ this.ensureInitialized();
2131
+ const results = await this.hookPipeline.runPluginUninstall(pluginId, deleteData);
2132
+ await this.deleteCronTasks(pluginId);
2133
+ this.plugins.delete(pluginId);
2134
+ this.initialized = false;
2135
+ await this.resolveExclusiveHooks();
2136
+ return results;
2137
+ }
2138
+ /**
2139
+ * Run content:beforeSave hooks across all active plugins
2140
+ */
2141
+ async runContentBeforeSave(content, collection, isNew) {
2142
+ this.ensureInitialized();
2143
+ return this.hookPipeline.runContentBeforeSave(content, collection, isNew);
2144
+ }
2145
+ /**
2146
+ * Run content:afterSave hooks across all active plugins
2147
+ */
2148
+ async runContentAfterSave(content, collection, isNew) {
2149
+ this.ensureInitialized();
2150
+ return this.hookPipeline.runContentAfterSave(content, collection, isNew);
2151
+ }
2152
+ /**
2153
+ * Run content:beforeDelete hooks across all active plugins
2154
+ */
2155
+ async runContentBeforeDelete(id, collection) {
2156
+ this.ensureInitialized();
2157
+ return this.hookPipeline.runContentBeforeDelete(id, collection);
2158
+ }
2159
+ /**
2160
+ * Run content:afterDelete hooks across all active plugins
2161
+ */
2162
+ async runContentAfterDelete(id, collection, permanent) {
2163
+ this.ensureInitialized();
2164
+ return this.hookPipeline.runContentAfterDelete(id, collection, permanent);
2165
+ }
2166
+ /**
2167
+ * Run content:afterPublish hooks across all active plugins
2168
+ */
2169
+ async runContentAfterPublish(content, collection) {
2170
+ this.ensureInitialized();
2171
+ return this.hookPipeline.runContentAfterPublish(content, collection);
2172
+ }
2173
+ /**
2174
+ * Run content:afterUnpublish hooks across all active plugins
2175
+ */
2176
+ async runContentAfterUnpublish(content, collection) {
2177
+ this.ensureInitialized();
2178
+ return this.hookPipeline.runContentAfterUnpublish(content, collection);
2179
+ }
2180
+ /**
2181
+ * Run media:beforeUpload hooks across all active plugins
2182
+ */
2183
+ async runMediaBeforeUpload(file) {
2184
+ this.ensureInitialized();
2185
+ return this.hookPipeline.runMediaBeforeUpload(file);
2186
+ }
2187
+ /**
2188
+ * Run media:afterUpload hooks across all active plugins
2189
+ */
2190
+ async runMediaAfterUpload(media) {
2191
+ this.ensureInitialized();
2192
+ return this.hookPipeline.runMediaAfterUpload(media);
2193
+ }
2194
+ /**
2195
+ * Invoke the cron hook for a specific plugin (per-plugin dispatch).
2196
+ * Used as the InvokeCronHookFn callback for CronExecutor.
2197
+ */
2198
+ async invokeCronHook(pluginId, event) {
2199
+ this.ensureInitialized();
2200
+ const result = await this.hookPipeline.invokeCronHook(pluginId, event);
2201
+ if (!result.success && result.error) throw result.error;
2202
+ }
2203
+ /**
2204
+ * Invoke a plugin route
2205
+ */
2206
+ async invokeRoute(pluginId, routeName, options) {
2207
+ this.ensureInitialized();
2208
+ return this.routeRegistry.invoke(pluginId, routeName, options);
2209
+ }
2210
+ /**
2211
+ * Get all routes for a plugin
2212
+ */
2213
+ getPluginRoutes(pluginId) {
2214
+ this.ensureInitialized();
2215
+ return this.routeRegistry.getRoutes(pluginId);
2216
+ }
2217
+ /**
2218
+ * Get a plugin by ID
2219
+ */
2220
+ getPlugin(pluginId) {
2221
+ return this.plugins.get(pluginId)?.plugin;
2222
+ }
2223
+ /**
2224
+ * Get plugin state
2225
+ */
2226
+ getPluginState(pluginId) {
2227
+ return this.plugins.get(pluginId)?.state;
2228
+ }
2229
+ /**
2230
+ * Get all registered plugins
2231
+ */
2232
+ getAllPlugins() {
2233
+ return Array.from(this.plugins.values(), (entry) => ({
2234
+ plugin: entry.plugin,
2235
+ state: entry.state
2236
+ }));
2237
+ }
2238
+ /**
2239
+ * Get all active plugins
2240
+ */
2241
+ getActivePlugins() {
2242
+ return [...this.plugins.values()].filter((entry) => entry.state === "active").map((entry) => entry.plugin);
2243
+ }
2244
+ /**
2245
+ * Check if a plugin exists
2246
+ */
2247
+ hasPlugin(pluginId) {
2248
+ return this.plugins.has(pluginId);
2249
+ }
2250
+ /**
2251
+ * Check if a plugin is active
2252
+ */
2253
+ isActive(pluginId) {
2254
+ return this.plugins.get(pluginId)?.state === "active";
2255
+ }
2256
+ /**
2257
+ * Get all plugins that registered a handler for an exclusive hook.
2258
+ */
2259
+ getExclusiveHookProviders(hookName) {
2260
+ this.ensureInitialized();
2261
+ return this.hookPipeline.getExclusiveHookProviders(hookName).map((p) => {
2262
+ const plugin = this.plugins.get(p.pluginId);
2263
+ return {
2264
+ pluginId: p.pluginId,
2265
+ pluginName: plugin?.plugin.id ?? p.pluginId
2266
+ };
2267
+ });
2268
+ }
2269
+ /**
2270
+ * Read the selected provider for an exclusive hook from the options table.
2271
+ */
2272
+ async getExclusiveHookSelection(hookName) {
2273
+ return new OptionsRepository(this.options.db).get(`${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`);
2274
+ }
2275
+ /**
2276
+ * Set the selected provider for an exclusive hook in the options table.
2277
+ * Pass null to clear the selection.
2278
+ */
2279
+ async setExclusiveHookSelection(hookName, pluginId) {
2280
+ const optionsRepo = new OptionsRepository(this.options.db);
2281
+ const key = `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`;
2282
+ if (pluginId === null) {
2283
+ await optionsRepo.delete(key);
2284
+ this.hookPipeline?.clearExclusiveSelection(hookName);
2285
+ return;
2286
+ }
2287
+ const entry = this.plugins.get(pluginId);
2288
+ if (!entry) throw new Error(`Plugin "${pluginId}" not found`);
2289
+ if (entry.state !== "active") throw new Error(`Plugin "${pluginId}" is not active`);
2290
+ await optionsRepo.set(key, pluginId);
2291
+ this.hookPipeline?.setExclusiveSelection(hookName, pluginId);
2292
+ }
2293
+ /**
2294
+ * Resolution algorithm for exclusive hooks.
2295
+ *
2296
+ * Delegates to the shared resolveExclusiveHooks() function.
2297
+ * See hooks.ts for the full algorithm description.
2298
+ */
2299
+ async resolveExclusiveHooks(preferredHints) {
2300
+ this.ensureInitialized();
2301
+ const optionsRepo = new OptionsRepository(this.options.db);
2302
+ await resolveExclusiveHooks({
2303
+ pipeline: this.hookPipeline,
2304
+ isActive: (pluginId) => this.isActive(pluginId),
2305
+ getOption: (key) => optionsRepo.get(key),
2306
+ setOption: (key, value) => optionsRepo.set(key, value),
2307
+ deleteOption: async (key) => {
2308
+ await optionsRepo.delete(key);
2309
+ },
2310
+ preferredHints
2311
+ });
2312
+ }
2313
+ /**
2314
+ * Get all exclusive hooks with their providers and current selections.
2315
+ * Used by the admin API.
2316
+ */
2317
+ async getExclusiveHooksInfo() {
2318
+ this.ensureInitialized();
2319
+ const exclusiveHookNames = this.hookPipeline.getRegisteredExclusiveHooks();
2320
+ const result = [];
2321
+ for (const hookName of exclusiveHookNames) {
2322
+ const providers = this.hookPipeline.getExclusiveHookProviders(hookName);
2323
+ const selection = await this.getExclusiveHookSelection(hookName);
2324
+ result.push({
2325
+ hookName,
2326
+ providers,
2327
+ selectedPluginId: selection
2328
+ });
2329
+ }
2330
+ return result;
2331
+ }
2332
+ /**
2333
+ * Initialize or reinitialize the hook pipeline and route registry
2334
+ */
2335
+ ensureInitialized() {
2336
+ if (this.initialized) return;
2337
+ const activePlugins = this.getActivePlugins();
2338
+ this.hookPipeline = new HookPipeline(activePlugins, this.factoryOptions);
2339
+ this.routeRegistry = new PluginRouteRegistry(this.factoryOptions);
2340
+ for (const plugin of activePlugins) this.routeRegistry.register(plugin);
2341
+ this.initialized = true;
2342
+ }
2343
+ /**
2344
+ * Force reinitialization (useful after plugin state changes)
2345
+ */
2346
+ reinitialize() {
2347
+ this.initialized = false;
2348
+ this.ensureInitialized();
2349
+ }
2350
+ /**
2351
+ * Delete all cron tasks for a plugin.
2352
+ * Used during uninstall.
2353
+ */
2354
+ async deleteCronTasks(pluginId) {
2355
+ try {
2356
+ await sql`
2357
+ DELETE FROM _dineway_cron_tasks
2358
+ WHERE plugin_id = ${pluginId}
2359
+ `.execute(this.options.db);
2360
+ } catch {}
2361
+ }
2362
+ };
2363
+ /**
2364
+ * Create a plugin manager
2365
+ */
2366
+ function createPluginManager(options) {
2367
+ return new PluginManager(options);
2368
+ }
2369
+
2370
+ //#endregion
2371
+ //#region src/plugins/sandbox/noop.ts
2372
+ /**
2373
+ * Error thrown when attempting to use sandboxing on an unsupported platform.
2374
+ */
2375
+ var SandboxNotAvailableError = class extends Error {
2376
+ constructor() {
2377
+ super("Plugin sandboxing is not available on this deployment. Marketplace-style plugins require a configured isolated SandboxRunner. Use trusted plugins from config until the portable Node runner lands.");
2378
+ this.name = "SandboxNotAvailableError";
2379
+ }
2380
+ };
2381
+ /**
2382
+ * No-op sandbox runner for platforms without isolation support.
2383
+ *
2384
+ * - `isAvailable()` returns false
2385
+ * - `load()` throws SandboxNotAvailableError
2386
+ * - `terminateAll()` is a no-op
2387
+ *
2388
+ * This is the default runner when no platform adapter is configured.
2389
+ */
2390
+ var NoopSandboxRunner = class {
2391
+ /**
2392
+ * Always returns false - sandboxing is not available.
2393
+ */
2394
+ isAvailable() {
2395
+ return false;
2396
+ }
2397
+ /**
2398
+ * Always throws - can't load sandboxed plugins without isolation.
2399
+ */
2400
+ async load(_manifest, _code) {
2401
+ throw new SandboxNotAvailableError();
2402
+ }
2403
+ /**
2404
+ * No-op - sandboxing not available, email callback is irrelevant.
2405
+ */
2406
+ setEmailSend() {}
2407
+ /**
2408
+ * No-op - nothing to terminate.
2409
+ */
2410
+ async terminateAll() {}
2411
+ };
2412
+ /**
2413
+ * Create a no-op sandbox runner.
2414
+ * This is used as the default when no platform adapter is configured.
2415
+ */
2416
+ function createNoopSandboxRunner(_options) {
2417
+ return new NoopSandboxRunner();
2418
+ }
2419
+
2420
+ //#endregion
2421
+ //#region src/plugins/sandbox/wrapper.ts
2422
+ const COMMENT_CLOSE_RE = /\*\//g;
2423
+ const NEWLINE_RE = /[\n\r]/g;
2424
+ const TRAILING_SLASH_RE = /\/$/;
2425
+ const DEFAULT_PLUGIN_MODULE_SPECIFIER = "sandbox-plugin.js";
2426
+ const DEFAULT_RUNTIME_GLOBAL$1 = "__DINEWAY_SANDBOX__";
2427
+ function generateSandboxWrapper(manifest, options = {}) {
2428
+ const storageCollections = Object.keys(manifest.storage ?? {});
2429
+ const pluginModuleSpecifier = options.pluginModuleSpecifier ?? DEFAULT_PLUGIN_MODULE_SPECIFIER;
2430
+ const runtimeGlobal = options.runtimeGlobal ?? DEFAULT_RUNTIME_GLOBAL$1;
2431
+ const site = options.site ?? {
2432
+ name: "",
2433
+ url: "",
2434
+ locale: "en"
2435
+ };
2436
+ const hasReadContent = manifest.capabilities.includes("content:read") || manifest.capabilities.includes("content:write");
2437
+ const hasWriteContent = manifest.capabilities.includes("content:write");
2438
+ const hasReadMedia = manifest.capabilities.includes("media:read") || manifest.capabilities.includes("media:write");
2439
+ const hasWriteMedia = manifest.capabilities.includes("media:write");
2440
+ const hasHttp = manifest.capabilities.includes("network:request") || manifest.capabilities.includes("network:request:unrestricted");
2441
+ const hasReadUsers = manifest.capabilities.includes("users:read");
2442
+ const hasEmailSend = manifest.capabilities.includes("email:send");
2443
+ return `
2444
+ // =============================================================================
2445
+ // Sandboxed Plugin Wrapper
2446
+ // Plugin: ${sanitizeComment(manifest.id)}@${sanitizeComment(manifest.version)}
2447
+ // =============================================================================
2448
+
2449
+ import pluginModule from ${JSON.stringify(pluginModuleSpecifier)};
2450
+
2451
+ const hooks = pluginModule?.hooks || pluginModule?.default?.hooks || {};
2452
+ const routes = pluginModule?.routes || pluginModule?.default?.routes || {};
2453
+ const storageCollections = ${JSON.stringify(storageCollections)};
2454
+ const fallbackPlugin = ${JSON.stringify({
2455
+ id: manifest.id,
2456
+ version: manifest.version
2457
+ })};
2458
+ const fallbackSite = ${JSON.stringify(site)};
2459
+
2460
+ function getRuntime() {
2461
+ const runtime = globalThis[${JSON.stringify(runtimeGlobal)}];
2462
+ if (!runtime || typeof runtime !== "object") {
2463
+ throw new Error("Sandbox runtime bridge is not configured.");
2464
+ }
2465
+ return runtime;
2466
+ }
2467
+
2468
+ function createHeaders(headerRecord) {
2469
+ const normalized = new Map(
2470
+ Object.entries(headerRecord || {}).map(([key, value]) => [key.toLowerCase(), value]),
2471
+ );
2472
+ return {
2473
+ get(name) {
2474
+ return normalized.get(String(name).toLowerCase()) ?? null;
2475
+ },
2476
+ has(name) {
2477
+ return normalized.has(String(name).toLowerCase());
2478
+ },
2479
+ entries() {
2480
+ return normalized.entries();
2481
+ },
2482
+ keys() {
2483
+ return normalized.keys();
2484
+ },
2485
+ values() {
2486
+ return normalized.values();
2487
+ },
2488
+ forEach(callback) {
2489
+ for (const [key, value] of normalized.entries()) {
2490
+ callback(value, key);
2491
+ }
2492
+ },
2493
+ };
2494
+ }
2495
+
2496
+ function createContext() {
2497
+ const runtime = getRuntime();
2498
+ const bridge = runtime.bridge;
2499
+ const plugin = runtime.plugin || fallbackPlugin;
2500
+ const site = runtime.site || fallbackSite;
2501
+ const siteBaseUrl = String(site.url || "").replace(${TRAILING_SLASH_RE}, "");
2502
+
2503
+ const kv = {
2504
+ get: (key) => bridge.kvGet(key),
2505
+ set: (key, value) => bridge.kvSet(key, value),
2506
+ delete: (key) => bridge.kvDelete(key),
2507
+ list: (prefix) => bridge.kvList(prefix),
2508
+ };
2509
+
2510
+ function createStorageCollection(collectionName) {
2511
+ if (!storageCollections.includes(collectionName)) {
2512
+ throw new Error('Storage collection "' + collectionName + '" is not declared in the plugin manifest.');
2513
+ }
2514
+ return {
2515
+ get: (id) => bridge.storageGet(collectionName, id),
2516
+ put: (id, data) => bridge.storagePut(collectionName, id, data),
2517
+ delete: (id) => bridge.storageDelete(collectionName, id),
2518
+ exists: async (id) => (await bridge.storageGet(collectionName, id)) !== null,
2519
+ query: (options) => bridge.storageQuery(collectionName, options),
2520
+ count: (where) => bridge.storageCount(collectionName, where),
2521
+ getMany: async (ids) => {
2522
+ const items = await bridge.storageGetMany(collectionName, ids);
2523
+ return new Map(items.map((item) => [item.id, item.data]));
2524
+ },
2525
+ putMany: (items) => bridge.storagePutMany(collectionName, items),
2526
+ deleteMany: (ids) => bridge.storageDeleteMany(collectionName, ids),
2527
+ };
2528
+ }
2529
+
2530
+ const storage = new Proxy({}, {
2531
+ get(_, collectionName) {
2532
+ if (typeof collectionName !== "string") return undefined;
2533
+ return createStorageCollection(collectionName);
2534
+ },
2535
+ });
2536
+
2537
+ const content = ${hasReadContent ? `{
2538
+ get: (collection, id) => bridge.contentGet(collection, id),
2539
+ list: (collection, options) => bridge.contentList(collection, options),${hasWriteContent ? `
2540
+ create: (collection, data) => bridge.contentCreate(collection, data),
2541
+ update: (collection, id, data) => bridge.contentUpdate(collection, id, data),
2542
+ delete: (collection, id) => bridge.contentDelete(collection, id),` : ""}
2543
+ }` : "undefined"};
2544
+
2545
+ const media = ${hasReadMedia ? `{
2546
+ get: (id) => bridge.mediaGet(id),
2547
+ list: (options) => bridge.mediaList(options),${hasWriteMedia ? `
2548
+ getUploadUrl: () => {
2549
+ throw new Error("getUploadUrl is not available in sandbox mode. Use media.upload(filename, contentType, bytes) instead.");
2550
+ },
2551
+ upload: (filename, contentType, bytes) => bridge.mediaUpload(filename, contentType, bytes),
2552
+ delete: (id) => bridge.mediaDelete(id),` : ""}
2553
+ }` : "undefined"};
2554
+
2555
+ const http = ${hasHttp ? `{
2556
+ fetch: async (url, init) => {
2557
+ const result = await bridge.httpFetch(url, init);
2558
+ return {
2559
+ status: result.status,
2560
+ ok: result.status >= 200 && result.status < 300,
2561
+ headers: createHeaders(result.headers),
2562
+ text: async () => result.text,
2563
+ json: async () => JSON.parse(result.text),
2564
+ };
2565
+ },
2566
+ }` : "undefined"};
2567
+
2568
+ const log = {
2569
+ debug: (message, data) => bridge.log("debug", message, data),
2570
+ info: (message, data) => bridge.log("info", message, data),
2571
+ warn: (message, data) => bridge.log("warn", message, data),
2572
+ error: (message, data) => bridge.log("error", message, data),
2573
+ };
2574
+
2575
+ const users = ${hasReadUsers ? `{
2576
+ get: (id) => bridge.userGet(id),
2577
+ getByEmail: (email) => bridge.userGetByEmail(email),
2578
+ list: (options) => bridge.userList(options),
2579
+ }` : "undefined"};
2580
+
2581
+ const cron =
2582
+ typeof bridge.cronSchedule === "function" &&
2583
+ typeof bridge.cronCancel === "function" &&
2584
+ typeof bridge.cronList === "function"
2585
+ ? {
2586
+ schedule: (name, options) => bridge.cronSchedule(name, options),
2587
+ cancel: (name) => bridge.cronCancel(name),
2588
+ list: () => bridge.cronList(),
2589
+ }
2590
+ : undefined;
2591
+
2592
+ const email = ${hasEmailSend ? `{
2593
+ send: (message) => bridge.emailSend(message),
2594
+ }` : "undefined"};
2595
+
2596
+ function url(path) {
2597
+ if (!path.startsWith("/")) {
2598
+ throw new Error('URL path must start with "/", got: "' + path + '"');
2599
+ }
2600
+ if (path.startsWith("//")) {
2601
+ throw new Error('URL path must not be protocol-relative, got: "' + path + '"');
2602
+ }
2603
+ return siteBaseUrl + path;
2604
+ }
2605
+
2606
+ return {
2607
+ plugin,
2608
+ storage,
2609
+ kv,
2610
+ content,
2611
+ media,
2612
+ http,
2613
+ log,
2614
+ site,
2615
+ url,
2616
+ users,
2617
+ cron,
2618
+ email,
2619
+ };
2620
+ }
2621
+
2622
+ export async function invokeHook(hookName, event) {
2623
+ const ctx = createContext();
2624
+ const hookDef = hooks[hookName];
2625
+ if (!hookDef) {
2626
+ return undefined;
2627
+ }
2628
+ const handler = typeof hookDef === "function" ? hookDef : hookDef.handler;
2629
+ if (typeof handler !== "function") {
2630
+ throw new Error(\`Hook \${hookName} handler is not a function\`);
2631
+ }
2632
+ return handler(event, ctx);
2633
+ }
2634
+
2635
+ export async function invokeRoute(routeName, input, serializedRequest) {
2636
+ const ctx = createContext();
2637
+ const route = routes[routeName];
2638
+ if (!route) {
2639
+ throw new Error(\`Route not found: \${routeName}\`);
2640
+ }
2641
+ const handler = typeof route === "function" ? route : route.handler;
2642
+ if (typeof handler !== "function") {
2643
+ throw new Error(\`Route \${routeName} handler is not a function\`);
2644
+ }
2645
+ return handler(
2646
+ { input, request: serializedRequest, requestMeta: serializedRequest.meta },
2647
+ ctx,
2648
+ );
2649
+ }
2650
+ `;
2651
+ }
2652
+ function sanitizeComment(value) {
2653
+ return value.replace(NEWLINE_RE, " ").replace(COMMENT_CLOSE_RE, "* /");
2654
+ }
2655
+
2656
+ //#endregion
2657
+ //#region src/plugins/sandbox/node.ts
2658
+ const DEFAULT_RUNTIME_GLOBAL = "__DINEWAY_SANDBOX__";
2659
+ const DEFAULT_LIMITS = {
2660
+ cpuMs: 50,
2661
+ memoryMb: 128,
2662
+ subrequests: 10,
2663
+ wallTimeMs: 3e4
2664
+ };
2665
+ const DINEWAY_SHIM_CODE = "export const definePlugin = (definition) => definition;\nexport default definePlugin;\n";
2666
+ const DINEWAY_STATIC_IMPORT_RE = /\bfrom\s+(['"])dineway\1/g;
2667
+ const DINEWAY_DYNAMIC_IMPORT_RE = /\bimport\(\s*(['"])dineway\1\s*\)/g;
2668
+ const ALL_BRIDGE_METHODS = [
2669
+ "kvGet",
2670
+ "kvSet",
2671
+ "kvDelete",
2672
+ "kvList",
2673
+ "storageGet",
2674
+ "storagePut",
2675
+ "storageDelete",
2676
+ "storageQuery",
2677
+ "storageCount",
2678
+ "storageGetMany",
2679
+ "storagePutMany",
2680
+ "storageDeleteMany",
2681
+ "contentGet",
2682
+ "contentList",
2683
+ "contentCreate",
2684
+ "contentUpdate",
2685
+ "contentDelete",
2686
+ "mediaGet",
2687
+ "mediaList",
2688
+ "mediaUpload",
2689
+ "mediaDelete",
2690
+ "httpFetch",
2691
+ "userGet",
2692
+ "userGetByEmail",
2693
+ "userList",
2694
+ "cronSchedule",
2695
+ "cronCancel",
2696
+ "cronList",
2697
+ "emailSend",
2698
+ "log"
2699
+ ];
2700
+ const WORKER_SOURCE = `
2701
+ const { parentPort, workerData } = require("node:worker_threads");
2702
+
2703
+ function toDataUrl(code) {
2704
+ return "data:text/javascript;base64," + Buffer.from(code, "utf8").toString("base64");
2705
+ }
2706
+
2707
+ function serializeError(error) {
2708
+ if (error && typeof error === "object") {
2709
+ return {
2710
+ message: typeof error.message === "string" ? error.message : String(error),
2711
+ stack: typeof error.stack === "string" ? error.stack : undefined,
2712
+ };
2713
+ }
2714
+ return { message: String(error) };
2715
+ }
2716
+
2717
+ const pendingBridgeCalls = new Map();
2718
+ let nextBridgeCallId = 1;
2719
+
2720
+ function callBridge(method, args) {
2721
+ return new Promise((resolve, reject) => {
2722
+ const id = nextBridgeCallId++;
2723
+ pendingBridgeCalls.set(id, { resolve, reject });
2724
+ parentPort.postMessage({ type: "bridge", id, method, args });
2725
+ });
2726
+ }
2727
+
2728
+ parentPort.on("message", (message) => {
2729
+ if (!message || typeof message !== "object") {
2730
+ return;
2731
+ }
2732
+
2733
+ if (message.type === "bridge-result") {
2734
+ const pending = pendingBridgeCalls.get(message.id);
2735
+ if (!pending) return;
2736
+ pendingBridgeCalls.delete(message.id);
2737
+ pending.resolve(message.value);
2738
+ return;
2739
+ }
2740
+
2741
+ if (message.type === "bridge-error") {
2742
+ const pending = pendingBridgeCalls.get(message.id);
2743
+ if (!pending) return;
2744
+ pendingBridgeCalls.delete(message.id);
2745
+ const error = new Error(
2746
+ message.error && typeof message.error.message === "string"
2747
+ ? message.error.message
2748
+ : "Sandbox bridge call failed",
2749
+ );
2750
+ if (message.error && typeof message.error.stack === "string") {
2751
+ error.stack = message.error.stack;
2752
+ }
2753
+ pending.reject(error);
2754
+ }
2755
+ });
2756
+
2757
+ const bridgeMethods = new Set(workerData.bridgeMethods);
2758
+ const bridge = new Proxy(
2759
+ {},
2760
+ {
2761
+ get(_target, prop) {
2762
+ if (typeof prop !== "string" || !bridgeMethods.has(prop)) {
2763
+ return undefined;
2764
+ }
2765
+ return (...args) => callBridge(prop, args);
2766
+ },
2767
+ },
2768
+ );
2769
+
2770
+ globalThis[workerData.runtimeGlobal] = {
2771
+ bridge,
2772
+ plugin: workerData.plugin,
2773
+ site: workerData.site,
2774
+ };
2775
+
2776
+ (async () => {
2777
+ try {
2778
+ const wrapperModule = await import(toDataUrl(workerData.wrapperCode));
2779
+ const invocation = workerData.invocation;
2780
+ const result =
2781
+ invocation.kind === "bootstrap"
2782
+ ? null
2783
+ : invocation.kind === "hook"
2784
+ ? await wrapperModule.invokeHook(invocation.name, invocation.event)
2785
+ : await wrapperModule.invokeRoute(
2786
+ invocation.name,
2787
+ invocation.input,
2788
+ invocation.request,
2789
+ );
2790
+
2791
+ parentPort.postMessage({ type: "result", value: result });
2792
+ } catch (error) {
2793
+ parentPort.postMessage({ type: "error", error: serializeError(error) });
2794
+ }
2795
+ })();
2796
+ `;
2797
+ function normalizeLimits(limits) {
2798
+ return {
2799
+ cpuMs: limits?.cpuMs ?? DEFAULT_LIMITS.cpuMs,
2800
+ memoryMb: limits?.memoryMb ?? DEFAULT_LIMITS.memoryMb,
2801
+ subrequests: limits?.subrequests ?? DEFAULT_LIMITS.subrequests,
2802
+ wallTimeMs: limits?.wallTimeMs ?? DEFAULT_LIMITS.wallTimeMs
2803
+ };
2804
+ }
2805
+ function codeToDataUrl(code) {
2806
+ return `data:text/javascript;base64,${Buffer.from(code, "utf8").toString("base64")}`;
2807
+ }
2808
+ function rewriteDinewayImport(code) {
2809
+ const shimUrl = codeToDataUrl(DINEWAY_SHIM_CODE);
2810
+ return code.replace(DINEWAY_STATIC_IMPORT_RE, `from "${shimUrl}"`).replace(DINEWAY_DYNAMIC_IMPORT_RE, `import("${shimUrl}")`);
2811
+ }
2812
+ function normalizeCapabilities(capabilities) {
2813
+ const normalized = [...capabilities];
2814
+ if (normalized.includes("content:write") && !normalized.includes("content:read")) normalized.push("content:read");
2815
+ if (normalized.includes("media:write") && !normalized.includes("media:read")) normalized.push("media:read");
2816
+ if (normalized.includes("network:request:unrestricted") && !normalized.includes("network:request")) normalized.push("network:request");
2817
+ return normalized;
2818
+ }
2819
+ function createResolvedPluginFromManifest(manifest) {
2820
+ return {
2821
+ id: manifest.id,
2822
+ version: manifest.version,
2823
+ capabilities: normalizeCapabilities(manifest.capabilities),
2824
+ allowedHosts: manifest.allowedHosts,
2825
+ storage: manifest.storage,
2826
+ hooks: {},
2827
+ routes: {},
2828
+ admin: manifest.admin
2829
+ };
2830
+ }
2831
+ function createSandboxUploadUrlStub() {
2832
+ return async () => {
2833
+ throw new Error("getUploadUrl is not available in sandbox mode. Use media.upload(filename, contentType, bytes) instead.");
2834
+ };
2835
+ }
2836
+ function toErrorPayload(error) {
2837
+ if (error instanceof Error) return {
2838
+ message: error.message,
2839
+ stack: error.stack
2840
+ };
2841
+ return { message: String(error) };
2842
+ }
2843
+ function toArrayBuffer(value) {
2844
+ if (value instanceof ArrayBuffer) return value;
2845
+ if (ArrayBuffer.isView(value)) {
2846
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
2847
+ const copy = new Uint8Array(bytes.byteLength);
2848
+ copy.set(bytes);
2849
+ return copy.buffer;
2850
+ }
2851
+ throw new Error("Expected an ArrayBuffer-compatible value.");
2852
+ }
2853
+ function createSerializedHttpResponse(response) {
2854
+ return response.text().then((text) => ({
2855
+ status: response.status,
2856
+ headers: Object.fromEntries(response.headers.entries()),
2857
+ text
2858
+ }));
2859
+ }
2860
+ function postWorkerMessage(worker, message) {
2861
+ worker.postMessage(message);
2862
+ }
2863
+ function isWriteContentAccess(content) {
2864
+ return !!content && typeof content.create === "function";
2865
+ }
2866
+ function isWriteMediaAccess(media) {
2867
+ return !!media && typeof media.upload === "function" && typeof media.delete === "function";
2868
+ }
2869
+ function requireStorageCollection(storage, collection) {
2870
+ const value = storage[collection];
2871
+ if (!value) throw new Error(`Storage collection "${collection}" is not declared in the plugin manifest.`);
2872
+ return value;
2873
+ }
2874
+ function requireHttpAccess(http) {
2875
+ if (!http) throw new Error("HTTP access is not available for this plugin.");
2876
+ return http;
2877
+ }
2878
+ function requireUserAccess(users) {
2879
+ if (!users) throw new Error("User access is not available for this plugin.");
2880
+ return users;
2881
+ }
2882
+ function requireCronAccess(cron) {
2883
+ if (!cron) throw new Error("Cron access is not available for this plugin.");
2884
+ return cron;
2885
+ }
2886
+ function requireWriteContentAccess(content) {
2887
+ if (!isWriteContentAccess(content)) throw new Error("Content write access is not available for this plugin.");
2888
+ return content;
2889
+ }
2890
+ function requireWriteMediaAccess(media) {
2891
+ if (!isWriteMediaAccess(media)) throw new Error("Media write access is not available for this plugin.");
2892
+ return media;
2893
+ }
2894
+ function isRecord(value) {
2895
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2896
+ }
2897
+ function expectString(value, label) {
2898
+ if (typeof value !== "string") throw new Error(`Expected ${label} to be a string.`);
2899
+ return value;
2900
+ }
2901
+ function expectOptionalString(value, label) {
2902
+ if (value === void 0) return;
2903
+ return expectString(value, label);
2904
+ }
2905
+ function expectStringArray(value, label) {
2906
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new Error(`Expected ${label} to be an array of strings.`);
2907
+ return value;
2908
+ }
2909
+ function isWhereValue(value) {
2910
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return true;
2911
+ if (!isRecord(value)) return false;
2912
+ if ("in" in value) return Array.isArray(value.in) && value.in.every((item) => typeof item === "string" || typeof item === "number");
2913
+ if ("startsWith" in value) return typeof value.startsWith === "string";
2914
+ return [
2915
+ "gt",
2916
+ "gte",
2917
+ "lt",
2918
+ "lte"
2919
+ ].some((key) => key in value) && Object.entries(value).every(([key, item]) => {
2920
+ if (![
2921
+ "gt",
2922
+ "gte",
2923
+ "lt",
2924
+ "lte"
2925
+ ].includes(key)) return false;
2926
+ return item === void 0 || typeof item === "string" || typeof item === "number";
2927
+ });
2928
+ }
2929
+ function isWhereClause(value) {
2930
+ return isRecord(value) && Object.values(value).every(isWhereValue);
2931
+ }
2932
+ function isOrderByRecord(value) {
2933
+ return isRecord(value) && Object.values(value).every((item) => item === "asc" || item === "desc");
2934
+ }
2935
+ function isQueryOptions(value) {
2936
+ if (!isRecord(value)) return false;
2937
+ if ("where" in value && value.where !== void 0 && !isWhereClause(value.where)) return false;
2938
+ if ("orderBy" in value && value.orderBy !== void 0 && !isOrderByRecord(value.orderBy)) return false;
2939
+ if ("limit" in value && value.limit !== void 0 && typeof value.limit !== "number") return false;
2940
+ if ("cursor" in value && value.cursor !== void 0 && typeof value.cursor !== "string") return false;
2941
+ return true;
2942
+ }
2943
+ function isContentListOptions(value) {
2944
+ if (!isRecord(value)) return false;
2945
+ if ("where" in value && value.where !== void 0 && !isContentListWhere(value.where)) return false;
2946
+ if ("limit" in value && value.limit !== void 0 && typeof value.limit !== "number") return false;
2947
+ if ("cursor" in value && value.cursor !== void 0 && typeof value.cursor !== "string") return false;
2948
+ if ("orderBy" in value && value.orderBy !== void 0 && !isOrderByRecord(value.orderBy)) return false;
2949
+ return true;
2950
+ }
2951
+ function isContentListWhere(value) {
2952
+ if (!isRecord(value)) return false;
2953
+ return Object.entries(value).every(([key, item]) => {
2954
+ if (key !== "status" && key !== "locale") return false;
2955
+ return item === void 0 || typeof item === "string";
2956
+ });
2957
+ }
2958
+ function isContentWriteInput(value) {
2959
+ return isRecord(value);
2960
+ }
2961
+ function isMediaListOptions(value) {
2962
+ if (!isRecord(value)) return false;
2963
+ if ("limit" in value && value.limit !== void 0 && typeof value.limit !== "number") return false;
2964
+ if ("cursor" in value && value.cursor !== void 0 && typeof value.cursor !== "string") return false;
2965
+ if ("mimeType" in value && value.mimeType !== void 0 && typeof value.mimeType !== "string") return false;
2966
+ return true;
2967
+ }
2968
+ function isStoragePutManyItems(value) {
2969
+ return Array.isArray(value) && value.every((item) => isRecord(item) && typeof item.id === "string" && "data" in item);
2970
+ }
2971
+ function isUserListOptions(value) {
2972
+ if (!isRecord(value)) return false;
2973
+ if ("role" in value && value.role !== void 0 && typeof value.role !== "number") return false;
2974
+ if ("limit" in value && value.limit !== void 0 && typeof value.limit !== "number") return false;
2975
+ if ("cursor" in value && value.cursor !== void 0 && typeof value.cursor !== "string") return false;
2976
+ return true;
2977
+ }
2978
+ function isCronScheduleOptions(value) {
2979
+ return isRecord(value) && typeof value.schedule === "string" && (value.data === void 0 || isRecord(value.data));
2980
+ }
2981
+ function isSandboxEmailMessage(value) {
2982
+ if (!isRecord(value)) return false;
2983
+ if (typeof value.to !== "string" || typeof value.subject !== "string" || typeof value.text !== "string") return false;
2984
+ return value.html === void 0 || typeof value.html === "string";
2985
+ }
2986
+ function isLogLevel(value) {
2987
+ return value === "debug" || value === "info" || value === "warn" || value === "error";
2988
+ }
2989
+ function expectOptional(value, label, guard) {
2990
+ if (value === void 0) return;
2991
+ if (!guard(value)) throw new Error(`Expected ${label} to match the required shape.`);
2992
+ return value;
2993
+ }
2994
+ function expectValue(value, label, guard) {
2995
+ if (!guard(value)) throw new Error(`Expected ${label} to match the required shape.`);
2996
+ return value;
2997
+ }
2998
+ var NodeSandboxRunner = class {
2999
+ limits;
3000
+ activeWorkers = /* @__PURE__ */ new Set();
3001
+ emailSend = null;
3002
+ constructor(options) {
3003
+ this.options = options;
3004
+ this.limits = normalizeLimits(options.limits);
3005
+ }
3006
+ isAvailable() {
3007
+ return true;
3008
+ }
3009
+ async load(manifest, code) {
3010
+ const plugin = new NodeSandboxedPlugin(manifest, code, this.options, this.limits, () => this.emailSend, this.activeWorkers);
3011
+ await plugin.validateLoad();
3012
+ return plugin;
3013
+ }
3014
+ setEmailSend(callback) {
3015
+ this.emailSend = callback;
3016
+ }
3017
+ async terminateAll() {
3018
+ await Promise.allSettled(Array.from(this.activeWorkers, (worker) => worker.terminate()));
3019
+ this.activeWorkers.clear();
3020
+ }
3021
+ };
3022
+ var NodeSandboxedPlugin = class {
3023
+ id;
3024
+ resolvedPlugin;
3025
+ pluginUrl;
3026
+ wrapperCode;
3027
+ pluginWorkers = /* @__PURE__ */ new Set();
3028
+ constructor(manifest, code, options, limits, getEmailSend, sharedActiveWorkers) {
3029
+ this.manifest = manifest;
3030
+ this.options = options;
3031
+ this.limits = limits;
3032
+ this.getEmailSend = getEmailSend;
3033
+ this.sharedActiveWorkers = sharedActiveWorkers;
3034
+ this.id = `${manifest.id}:${manifest.version}`;
3035
+ this.resolvedPlugin = createResolvedPluginFromManifest(manifest);
3036
+ this.pluginUrl = codeToDataUrl(rewriteDinewayImport(code));
3037
+ this.wrapperCode = generateSandboxWrapper(manifest, {
3038
+ pluginModuleSpecifier: this.pluginUrl,
3039
+ runtimeGlobal: DEFAULT_RUNTIME_GLOBAL,
3040
+ site: this.options.siteInfo
3041
+ });
3042
+ }
3043
+ async invokeHook(hookName, event) {
3044
+ return this.invokeInWorker({
3045
+ kind: "hook",
3046
+ name: hookName,
3047
+ event
3048
+ });
3049
+ }
3050
+ async invokeRoute(routeName, input, request) {
3051
+ return this.invokeInWorker({
3052
+ kind: "route",
3053
+ name: routeName,
3054
+ input,
3055
+ request
3056
+ });
3057
+ }
3058
+ async terminate() {
3059
+ await Promise.allSettled(Array.from(this.pluginWorkers, (worker) => worker.terminate()));
3060
+ this.pluginWorkers.clear();
3061
+ }
3062
+ async validateLoad() {
3063
+ await this.invokeInWorker({ kind: "bootstrap" });
3064
+ }
3065
+ createPluginContext() {
3066
+ return createPluginContext({
3067
+ db: this.options.db,
3068
+ storage: this.options.mediaStorage,
3069
+ getUploadUrl: this.manifest.capabilities.includes("media:write") ? createSandboxUploadUrlStub() : void 0,
3070
+ cronReschedule: this.options.cronReschedule,
3071
+ siteInfo: this.options.siteInfo
3072
+ }, this.resolvedPlugin);
3073
+ }
3074
+ async handleBridgeCall(method, args, state) {
3075
+ const ctx = this.createPluginContext();
3076
+ const stringArg = (index, label) => expectString(args[index], `${String(method)} ${label}`);
3077
+ const optionalStringArg = (index, label) => expectOptionalString(args[index], `${String(method)} ${label}`);
3078
+ switch (method) {
3079
+ case "kvGet": return ctx.kv.get(stringArg(0, "key"));
3080
+ case "kvSet": return ctx.kv.set(stringArg(0, "key"), args[1]);
3081
+ case "kvDelete": return ctx.kv.delete(stringArg(0, "key"));
3082
+ case "kvList": return ctx.kv.list(optionalStringArg(0, "prefix"));
3083
+ case "storageGet": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).get(stringArg(1, "id"));
3084
+ case "storagePut": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).put(stringArg(1, "id"), args[2]);
3085
+ case "storageDelete": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).delete(stringArg(1, "id"));
3086
+ case "storageQuery": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).query(expectOptional(args[1], `${String(method)} options`, isQueryOptions));
3087
+ case "storageCount": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).count(expectOptional(args[1], `${String(method)} where`, isWhereClause));
3088
+ case "storageGetMany": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).getMany(expectStringArray(args[1], `${String(method)} ids`)).then((items) => Array.from(items, ([id, data]) => ({
3089
+ id,
3090
+ data
3091
+ })));
3092
+ case "storagePutMany": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).putMany(expectValue(args[1], `${String(method)} items`, isStoragePutManyItems));
3093
+ case "storageDeleteMany": return requireStorageCollection(ctx.storage, stringArg(0, "collection")).deleteMany(expectStringArray(args[1], `${String(method)} ids`));
3094
+ case "contentGet": return ctx.content?.get(stringArg(0, "collection"), stringArg(1, "id")) ?? null;
3095
+ case "contentList": return ctx.content?.list(stringArg(0, "collection"), expectOptional(args[1], `${String(method)} options`, isContentListOptions)) ?? null;
3096
+ case "contentCreate": return requireWriteContentAccess(ctx.content).create(stringArg(0, "collection"), expectValue(args[1], `${String(method)} data`, isContentWriteInput));
3097
+ case "contentUpdate": return requireWriteContentAccess(ctx.content).update(stringArg(0, "collection"), stringArg(1, "id"), expectValue(args[2], `${String(method)} data`, isContentWriteInput));
3098
+ case "contentDelete": return requireWriteContentAccess(ctx.content).delete(stringArg(0, "collection"), stringArg(1, "id"));
3099
+ case "mediaGet": return ctx.media?.get(stringArg(0, "id")) ?? null;
3100
+ case "mediaList": return ctx.media?.list(expectOptional(args[0], `${String(method)} options`, isMediaListOptions)) ?? null;
3101
+ case "mediaUpload": return requireWriteMediaAccess(ctx.media).upload(stringArg(0, "filename"), stringArg(1, "contentType"), toArrayBuffer(args[2]));
3102
+ case "mediaDelete": return requireWriteMediaAccess(ctx.media).delete(stringArg(0, "id"));
3103
+ case "httpFetch": {
3104
+ const http = requireHttpAccess(ctx.http);
3105
+ state.subrequests += 1;
3106
+ if (state.subrequests > this.limits.subrequests) throw new Error(`Sandbox subrequest limit exceeded (${this.limits.subrequests} per invocation).`);
3107
+ return createSerializedHttpResponse(await http.fetch(stringArg(0, "url"), expectOptional(args[1], `${String(method)} init`, isRecord)));
3108
+ }
3109
+ case "userGet": return requireUserAccess(ctx.users).get(stringArg(0, "id"));
3110
+ case "userGetByEmail": return requireUserAccess(ctx.users).getByEmail(stringArg(0, "email"));
3111
+ case "userList": return requireUserAccess(ctx.users).list(expectOptional(args[0], `${String(method)} options`, isUserListOptions));
3112
+ case "cronSchedule": return requireCronAccess(ctx.cron).schedule(stringArg(0, "name"), expectValue(args[1], `${String(method)} options`, isCronScheduleOptions));
3113
+ case "cronCancel": return requireCronAccess(ctx.cron).cancel(stringArg(0, "name"));
3114
+ case "cronList": return requireCronAccess(ctx.cron).list();
3115
+ case "emailSend": {
3116
+ const callback = this.getEmailSend();
3117
+ if (!callback) throw new Error("Email sending is not configured for sandboxed plugins.");
3118
+ return callback(expectValue(args[0], `${String(method)} message`, isSandboxEmailMessage), this.manifest.id);
3119
+ }
3120
+ case "log": {
3121
+ const level = expectValue(args[0], `${String(method)} level`, isLogLevel);
3122
+ const message = stringArg(1, "message");
3123
+ const data = args[2];
3124
+ const fn = ctx.log[level];
3125
+ return fn(message, data);
3126
+ }
3127
+ default: {
3128
+ const unknownMethod = method;
3129
+ throw new Error(`Unsupported sandbox bridge method: ${String(unknownMethod)}`);
3130
+ }
3131
+ }
3132
+ }
3133
+ async invokeInWorker(invocation) {
3134
+ const state = { subrequests: 0 };
3135
+ return new Promise((resolve, reject) => {
3136
+ const worker = new Worker(WORKER_SOURCE, {
3137
+ eval: true,
3138
+ resourceLimits: { maxOldGenerationSizeMb: this.limits.memoryMb },
3139
+ workerData: {
3140
+ runtimeGlobal: DEFAULT_RUNTIME_GLOBAL,
3141
+ bridgeMethods: ALL_BRIDGE_METHODS,
3142
+ plugin: {
3143
+ id: this.manifest.id,
3144
+ version: this.manifest.version
3145
+ },
3146
+ site: this.options.siteInfo ?? {
3147
+ name: "",
3148
+ url: "",
3149
+ locale: "en"
3150
+ },
3151
+ wrapperCode: this.wrapperCode,
3152
+ invocation
3153
+ }
3154
+ });
3155
+ this.pluginWorkers.add(worker);
3156
+ this.sharedActiveWorkers.add(worker);
3157
+ let settled = false;
3158
+ let timedOut = false;
3159
+ let cpuTimer;
3160
+ const cleanup = () => {
3161
+ clearTimeout(timeout);
3162
+ if (cpuTimer) clearTimeout(cpuTimer);
3163
+ this.pluginWorkers.delete(worker);
3164
+ this.sharedActiveWorkers.delete(worker);
3165
+ worker.removeAllListeners();
3166
+ };
3167
+ const finish = (fn) => {
3168
+ if (settled) return;
3169
+ settled = true;
3170
+ cleanup();
3171
+ worker.terminate();
3172
+ fn();
3173
+ };
3174
+ const timeout = setTimeout(() => {
3175
+ timedOut = true;
3176
+ finish(() => {
3177
+ reject(/* @__PURE__ */ new Error(`Sandbox invocation timed out after ${this.limits.wallTimeMs}ms.`));
3178
+ });
3179
+ }, this.limits.wallTimeMs);
3180
+ const cpuPollIntervalMs = Math.max(5, Math.min(25, Math.floor(this.limits.cpuMs / 4)));
3181
+ const scheduleCpuCheck = () => {
3182
+ if (settled) return;
3183
+ cpuTimer = setTimeout(() => {
3184
+ try {
3185
+ if (worker.performance.eventLoopUtilization().active > this.limits.cpuMs) {
3186
+ finish(() => {
3187
+ reject(/* @__PURE__ */ new Error(`Sandbox invocation exceeded CPU limit of ${this.limits.cpuMs}ms.`));
3188
+ });
3189
+ return;
3190
+ }
3191
+ } catch {
3192
+ return;
3193
+ }
3194
+ scheduleCpuCheck();
3195
+ }, cpuPollIntervalMs);
3196
+ };
3197
+ scheduleCpuCheck();
3198
+ worker.on("message", (message) => {
3199
+ if (message.type === "bridge") {
3200
+ this.handleBridgeCall(message.method, message.args, state).then((value) => {
3201
+ postWorkerMessage(worker, {
3202
+ type: "bridge-result",
3203
+ id: message.id,
3204
+ value
3205
+ });
3206
+ }).catch((error) => {
3207
+ postWorkerMessage(worker, {
3208
+ type: "bridge-error",
3209
+ id: message.id,
3210
+ error: toErrorPayload(error)
3211
+ });
3212
+ });
3213
+ return;
3214
+ }
3215
+ if (message.type === "result") {
3216
+ finish(() => resolve(message.value));
3217
+ return;
3218
+ }
3219
+ if (message.type === "error") finish(() => {
3220
+ const error = new Error(message.error.message);
3221
+ if (message.error.stack) error.stack = message.error.stack;
3222
+ reject(error);
3223
+ });
3224
+ });
3225
+ worker.once("error", (error) => {
3226
+ finish(() => reject(error));
3227
+ });
3228
+ worker.once("exit", (code) => {
3229
+ if (settled || timedOut) return;
3230
+ finish(() => {
3231
+ reject(/* @__PURE__ */ new Error(`Sandbox worker exited unexpectedly with code ${code}.`));
3232
+ });
3233
+ });
3234
+ });
3235
+ }
3236
+ };
3237
+ function createNodeSandboxRunner(options) {
3238
+ return new NodeSandboxRunner(options);
3239
+ }
3240
+
3241
+ //#endregion
3242
+ //#region src/plugins/types.ts
3243
+ /**
3244
+ * Check if a value is a StandardPluginDefinition (has hooks/routes but no id/version).
3245
+ */
3246
+ function isStandardPluginDefinition(value) {
3247
+ if (typeof value !== "object" || value === null) return false;
3248
+ const hasPluginShape = "hooks" in value || "routes" in value;
3249
+ const hasNativeShape = "id" in value && "version" in value;
3250
+ return hasPluginShape && !hasNativeShape;
3251
+ }
3252
+
3253
+ //#endregion
3254
+ //#region src/comments/query.ts
3255
+ /**
3256
+ * Get approved comments for a content item.
3257
+ *
3258
+ * @example
3259
+ * ```ts
3260
+ * import { getComments } from "dineway";
3261
+ *
3262
+ * const { items, total } = await getComments({
3263
+ * collection: "posts",
3264
+ * contentId: post.id,
3265
+ * threaded: true,
3266
+ * });
3267
+ * ```
3268
+ */
3269
+ async function getComments(options) {
3270
+ return getCommentsWithDb(await getDb(), options);
3271
+ }
3272
+ /**
3273
+ * Get approved comments with an explicit db handle.
3274
+ *
3275
+ * @internal Use `getComments()` in templates. This variant is for routes
3276
+ * that already have a database handle.
3277
+ */
3278
+ async function getCommentsWithDb(db, options) {
3279
+ const repo = new CommentRepository(db);
3280
+ const total = await repo.countByContent(options.collection, options.contentId, "approved");
3281
+ const result = await repo.findByContent(options.collection, options.contentId, {
3282
+ status: "approved",
3283
+ limit: 500
3284
+ });
3285
+ if (options.threaded) return {
3286
+ items: CommentRepository.assembleThreads(result.items).map((c) => CommentRepository.toPublicComment(c)),
3287
+ total
3288
+ };
3289
+ return {
3290
+ items: result.items.map((c) => CommentRepository.toPublicComment(c)),
3291
+ total
3292
+ };
3293
+ }
3294
+ /**
3295
+ * Get the count of approved comments for a content item.
3296
+ *
3297
+ * @example
3298
+ * ```ts
3299
+ * import { getCommentCount } from "dineway";
3300
+ *
3301
+ * const count = await getCommentCount("posts", post.id);
3302
+ * ```
3303
+ */
3304
+ async function getCommentCount(collection, contentId) {
3305
+ return getCommentCountWithDb(await getDb(), collection, contentId);
3306
+ }
3307
+ /**
3308
+ * Get comment count with an explicit db handle.
3309
+ *
3310
+ * @internal Use `getCommentCount()` in templates.
3311
+ */
3312
+ async function getCommentCountWithDb(db, collection, contentId) {
3313
+ return new CommentRepository(db).countByContent(collection, contentId, "approved");
3314
+ }
3315
+
3316
+ //#endregion
3317
+ //#region src/menus/index.ts
3318
+ /**
3319
+ * Get menu by name with resolved URLs
3320
+ *
3321
+ * @example
3322
+ * ```ts
3323
+ * import { getMenu } from "dineway";
3324
+ *
3325
+ * const menu = await getMenu("primary");
3326
+ * if (menu) {
3327
+ * console.log(menu.items); // Array of MenuItem with resolved URLs
3328
+ * }
3329
+ * ```
3330
+ */
3331
+ function getMenu(name) {
3332
+ return requestCached(`menu:${name}`, async () => {
3333
+ return getMenuWithDb(name, await getDb());
3334
+ });
3335
+ }
3336
+ /**
3337
+ * Get menu by name with resolved URLs (with explicit db)
3338
+ *
3339
+ * @internal Use `getMenu()` in templates. This variant is for admin routes
3340
+ * that already have a database handle.
3341
+ */
3342
+ async function getMenuWithDb(name, db) {
3343
+ const menuRow = await db.selectFrom("_dineway_menus").selectAll().where("name", "=", name).executeTakeFirst();
3344
+ if (!menuRow) return null;
3345
+ const items = await buildMenuTree(await db.selectFrom("_dineway_menu_items").selectAll().$castTo().where("menu_id", "=", menuRow.id).orderBy("sort_order", "asc").execute(), db);
3346
+ return {
3347
+ id: menuRow.id,
3348
+ name: menuRow.name,
3349
+ label: menuRow.label,
3350
+ items
3351
+ };
3352
+ }
3353
+ /**
3354
+ * Get all menus (without items - for admin list)
3355
+ *
3356
+ * @example
3357
+ * ```ts
3358
+ * import { getMenus } from "dineway";
3359
+ *
3360
+ * const menus = await getMenus();
3361
+ * console.log(menus); // [{ id, name, label }]
3362
+ * ```
3363
+ */
3364
+ async function getMenus() {
3365
+ return getMenusWithDb(await getDb());
3366
+ }
3367
+ /**
3368
+ * Get all menus (with explicit db)
3369
+ *
3370
+ * @internal Use `getMenus()` in templates. This variant is for admin routes
3371
+ * that already have a database handle.
3372
+ */
3373
+ async function getMenusWithDb(db) {
3374
+ return await db.selectFrom("_dineway_menus").select([
3375
+ "id",
3376
+ "name",
3377
+ "label"
3378
+ ]).orderBy("name", "asc").execute();
3379
+ }
3380
+ /**
3381
+ * Build hierarchical menu tree from flat array of items
3382
+ */
3383
+ async function buildMenuTree(items, db) {
3384
+ const collectionSlugs = /* @__PURE__ */ new Set();
3385
+ for (const item of items) {
3386
+ if (item.reference_collection) collectionSlugs.add(item.reference_collection);
3387
+ if (item.type === "page" || item.type === "post") collectionSlugs.add(item.reference_collection || `${item.type}s`);
3388
+ }
3389
+ const urlPatterns = /* @__PURE__ */ new Map();
3390
+ if (collectionSlugs.size > 0) {
3391
+ const rows = await db.selectFrom("_dineway_collections").select(["slug", "url_pattern"]).where("slug", "in", [...collectionSlugs]).execute();
3392
+ for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
3393
+ }
3394
+ const validItems = (await Promise.all(items.map((item) => resolveMenuItem(item, db, urlPatterns)))).filter((item) => item !== null);
3395
+ const itemMap = /* @__PURE__ */ new Map();
3396
+ const rootItems = [];
3397
+ for (const item of validItems) itemMap.set(item.id, {
3398
+ ...item,
3399
+ children: []
3400
+ });
3401
+ for (const item of items) {
3402
+ const menuItem = itemMap.get(item.id);
3403
+ if (!menuItem) continue;
3404
+ if (item.parent_id) {
3405
+ const parent = itemMap.get(item.parent_id);
3406
+ if (parent) parent.children.push(menuItem);
3407
+ else rootItems.push(menuItem);
3408
+ } else rootItems.push(menuItem);
3409
+ }
3410
+ return rootItems;
3411
+ }
3412
+ /**
3413
+ * Resolve a single menu item's URL
3414
+ *
3415
+ * Returns null if the referenced content no longer exists (item should be skipped)
3416
+ */
3417
+ async function resolveMenuItem(item, db, urlPatterns) {
3418
+ let url;
3419
+ try {
3420
+ switch (item.type) {
3421
+ case "custom":
3422
+ url = item.custom_url || "#";
3423
+ break;
3424
+ case "page":
3425
+ case "post":
3426
+ url = await resolveContentUrl(item.reference_collection || `${item.type}s`, item.reference_id, db, urlPatterns);
3427
+ if (url === null) return null;
3428
+ break;
3429
+ case "taxonomy":
3430
+ url = await resolveTaxonomyUrl(item.reference_id, db);
3431
+ if (url === null) return null;
3432
+ break;
3433
+ case "collection":
3434
+ url = `/${item.reference_collection}/`;
3435
+ break;
3436
+ default: if (item.reference_collection && item.reference_id) {
3437
+ url = await resolveContentUrl(item.reference_collection, item.reference_id, db, urlPatterns);
3438
+ if (url === null) return null;
3439
+ } else url = "#";
3440
+ }
3441
+ } catch (error) {
3442
+ console.error(`Failed to resolve menu item ${item.id}:`, error);
3443
+ return null;
3444
+ }
3445
+ return {
3446
+ id: item.id,
3447
+ label: item.label,
3448
+ url: sanitizeHref(url),
3449
+ target: item.target || void 0,
3450
+ titleAttr: item.title_attr || void 0,
3451
+ cssClasses: item.css_classes || void 0,
3452
+ children: []
3453
+ };
3454
+ }
3455
+ const SLUG_PLACEHOLDER = /\{slug\}/g;
3456
+ const ID_PLACEHOLDER = /\{id\}/g;
3457
+ /**
3458
+ * Interpolate a URL pattern with entry data
3459
+ *
3460
+ * Replaces `{slug}` and `{id}` placeholders.
3461
+ */
3462
+ function interpolateUrlPattern(pattern, slug, id) {
3463
+ return pattern.replace(SLUG_PLACEHOLDER, slug).replace(ID_PLACEHOLDER, id);
3464
+ }
3465
+ /**
3466
+ * Resolve URL for a content entry (page/post)
3467
+ *
3468
+ * Uses the collection's url_pattern if set, otherwise falls back to /{collection}/{slug}.
3469
+ * Returns null if content not found (item should be skipped).
3470
+ */
3471
+ async function resolveContentUrl(collection, entryId, db, urlPatterns) {
3472
+ if (!entryId) return null;
3473
+ try {
3474
+ validateIdentifier(collection, "menu item collection");
3475
+ const row = (await sql`
3476
+ SELECT slug FROM ${sql.ref(`ec_${collection}`)} WHERE id = ${entryId} LIMIT 1
3477
+ `.execute(db)).rows[0];
3478
+ if (row) {
3479
+ const pattern = urlPatterns.get(collection);
3480
+ if (pattern) return interpolateUrlPattern(pattern, row.slug, entryId);
3481
+ return `/${collection}/${row.slug}`;
3482
+ }
3483
+ return null;
3484
+ } catch (error) {
3485
+ console.error(`Failed to resolve content URL for ${collection}/${entryId}:`, error);
3486
+ return null;
3487
+ }
3488
+ }
3489
+ /**
3490
+ * Resolve URL for a taxonomy term
3491
+ *
3492
+ * Returns null if taxonomy not found (item should be skipped)
3493
+ */
3494
+ async function resolveTaxonomyUrl(taxonomyId, db) {
3495
+ if (!taxonomyId) return null;
3496
+ const taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("id", "=", taxonomyId).executeTakeFirst();
3497
+ if (!taxonomy) return null;
3498
+ return `/${taxonomy.name}/${taxonomy.slug}`;
3499
+ }
3500
+
3501
+ //#endregion
3502
+ export { portableText as C, prosemirrorToPortableText as S, image as T, createHookPipeline as _, isStandardPluginDefinition as a, after as b, NoopSandboxRunner as c, PluginManager as d, createPluginManager as f, HookPipeline as g, EmailPipeline as h, getComments as i, SandboxNotAvailableError as l, PluginRouteRegistry as m, getMenus as n, NodeSandboxRunner as o, PluginRouteError as p, getCommentCount as r, createNodeSandboxRunner as s, getMenu as t, createNoopSandboxRunner as u, resolveExclusiveHooks as v, reference as w, portableTextToProsemirror as x, definePlugin as y };