emdash 0.0.0-a → 0.0.1

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 (661) hide show
  1. package/README.md +87 -1
  2. package/dist/adapters-BLMa4JGD.d.mts +106 -0
  3. package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
  4. package/dist/apply-Bjfq_b4-.mjs +1293 -0
  5. package/dist/apply-Bjfq_b4-.mjs.map +1 -0
  6. package/dist/astro/index.d.mts +51 -0
  7. package/dist/astro/index.d.mts.map +1 -0
  8. package/dist/astro/index.mjs +1333 -0
  9. package/dist/astro/index.mjs.map +1 -0
  10. package/dist/astro/middleware/auth.d.mts +31 -0
  11. package/dist/astro/middleware/auth.d.mts.map +1 -0
  12. package/dist/astro/middleware/auth.mjs +654 -0
  13. package/dist/astro/middleware/auth.mjs.map +1 -0
  14. package/dist/astro/middleware/redirect.d.mts +22 -0
  15. package/dist/astro/middleware/redirect.d.mts.map +1 -0
  16. package/dist/astro/middleware/redirect.mjs +63 -0
  17. package/dist/astro/middleware/redirect.mjs.map +1 -0
  18. package/dist/astro/middleware/request-context.d.mts +18 -0
  19. package/dist/astro/middleware/request-context.d.mts.map +1 -0
  20. package/dist/astro/middleware/request-context.mjs +1310 -0
  21. package/dist/astro/middleware/request-context.mjs.map +1 -0
  22. package/dist/astro/middleware/setup.d.mts +20 -0
  23. package/dist/astro/middleware/setup.d.mts.map +1 -0
  24. package/dist/astro/middleware/setup.mjs +47 -0
  25. package/dist/astro/middleware/setup.mjs.map +1 -0
  26. package/dist/astro/middleware.d.mts +13 -0
  27. package/dist/astro/middleware.d.mts.map +1 -0
  28. package/dist/astro/middleware.mjs +1613 -0
  29. package/dist/astro/middleware.mjs.map +1 -0
  30. package/dist/astro/types.d.mts +250 -0
  31. package/dist/astro/types.d.mts.map +1 -0
  32. package/dist/astro/types.mjs +1 -0
  33. package/dist/base64-MBPo9ozB.mjs +59 -0
  34. package/dist/base64-MBPo9ozB.mjs.map +1 -0
  35. package/dist/byline-CL847F26.mjs +213 -0
  36. package/dist/byline-CL847F26.mjs.map +1 -0
  37. package/dist/bylines-C2a-2TGt.mjs +136 -0
  38. package/dist/bylines-C2a-2TGt.mjs.map +1 -0
  39. package/dist/chunk-ClPoSABd.mjs +21 -0
  40. package/dist/cli/index.d.mts +1 -0
  41. package/dist/cli/index.mjs +3909 -0
  42. package/dist/cli/index.mjs.map +1 -0
  43. package/dist/client/cf-access.d.mts +60 -0
  44. package/dist/client/cf-access.d.mts.map +1 -0
  45. package/dist/client/cf-access.mjs +179 -0
  46. package/dist/client/cf-access.mjs.map +1 -0
  47. package/dist/client/index.d.mts +398 -0
  48. package/dist/client/index.d.mts.map +1 -0
  49. package/dist/client/index.mjs +346 -0
  50. package/dist/client/index.mjs.map +1 -0
  51. package/dist/config-CKE8p9xM.mjs +55 -0
  52. package/dist/config-CKE8p9xM.mjs.map +1 -0
  53. package/dist/connection-B4zVnQIa.mjs +40 -0
  54. package/dist/connection-B4zVnQIa.mjs.map +1 -0
  55. package/dist/content-D6C2WsZC.mjs +824 -0
  56. package/dist/content-D6C2WsZC.mjs.map +1 -0
  57. package/dist/db/index.d.mts +4 -0
  58. package/dist/db/index.mjs +62 -0
  59. package/dist/db/index.mjs.map +1 -0
  60. package/dist/db/libsql.d.mts +11 -0
  61. package/dist/db/libsql.d.mts.map +1 -0
  62. package/dist/db/libsql.mjs +17 -0
  63. package/dist/db/libsql.mjs.map +1 -0
  64. package/dist/db/postgres.d.mts +11 -0
  65. package/dist/db/postgres.d.mts.map +1 -0
  66. package/dist/db/postgres.mjs +30 -0
  67. package/dist/db/postgres.mjs.map +1 -0
  68. package/dist/db/sqlite.d.mts +11 -0
  69. package/dist/db/sqlite.d.mts.map +1 -0
  70. package/dist/db/sqlite.mjs +16 -0
  71. package/dist/db/sqlite.mjs.map +1 -0
  72. package/dist/default-Cyi4aAxu.mjs +81 -0
  73. package/dist/default-Cyi4aAxu.mjs.map +1 -0
  74. package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
  75. package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
  76. package/dist/error-Cxz0tQeO.mjs +27 -0
  77. package/dist/error-Cxz0tQeO.mjs.map +1 -0
  78. package/dist/index-C1xF3OGh.d.mts +4527 -0
  79. package/dist/index-C1xF3OGh.d.mts.map +1 -0
  80. package/dist/index.d.mts +16 -0
  81. package/dist/index.mjs +30 -0
  82. package/dist/load-yOOlckBj.mjs +28 -0
  83. package/dist/load-yOOlckBj.mjs.map +1 -0
  84. package/dist/loader-fz8Q_3EO.mjs +447 -0
  85. package/dist/loader-fz8Q_3EO.mjs.map +1 -0
  86. package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
  87. package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
  88. package/dist/media/index.d.mts +26 -0
  89. package/dist/media/index.d.mts.map +1 -0
  90. package/dist/media/index.mjs +55 -0
  91. package/dist/media/index.mjs.map +1 -0
  92. package/dist/media/local-runtime.d.mts +39 -0
  93. package/dist/media/local-runtime.d.mts.map +1 -0
  94. package/dist/media/local-runtime.mjs +133 -0
  95. package/dist/media/local-runtime.mjs.map +1 -0
  96. package/dist/media-DqHVh136.mjs +200 -0
  97. package/dist/media-DqHVh136.mjs.map +1 -0
  98. package/dist/mode-C2EzN1uE.mjs +23 -0
  99. package/dist/mode-C2EzN1uE.mjs.map +1 -0
  100. package/dist/page/index.d.mts +140 -0
  101. package/dist/page/index.d.mts.map +1 -0
  102. package/dist/page/index.mjs +416 -0
  103. package/dist/page/index.mjs.map +1 -0
  104. package/dist/placeholder-CmGAmqeO.d.mts +276 -0
  105. package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
  106. package/dist/placeholder-SmpOx-_v.mjs +243 -0
  107. package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
  108. package/dist/plugin-utils.d.mts +58 -0
  109. package/dist/plugin-utils.d.mts.map +1 -0
  110. package/dist/plugin-utils.mjs +78 -0
  111. package/dist/plugin-utils.mjs.map +1 -0
  112. package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
  113. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
  114. package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
  115. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
  116. package/dist/query-CS_iSj34.mjs +460 -0
  117. package/dist/query-CS_iSj34.mjs.map +1 -0
  118. package/dist/redirect-DIfIni3r.mjs +329 -0
  119. package/dist/redirect-DIfIni3r.mjs.map +1 -0
  120. package/dist/registry-D_w5HW4G.mjs +863 -0
  121. package/dist/registry-D_w5HW4G.mjs.map +1 -0
  122. package/dist/request-context.d.mts +49 -0
  123. package/dist/request-context.d.mts.map +1 -0
  124. package/dist/request-context.mjs +43 -0
  125. package/dist/request-context.mjs.map +1 -0
  126. package/dist/runner-B-u2F2b6.mjs +1412 -0
  127. package/dist/runner-B-u2F2b6.mjs.map +1 -0
  128. package/dist/runner-EAtf0ZIe.d.mts +27 -0
  129. package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
  130. package/dist/runtime.d.mts +26 -0
  131. package/dist/runtime.d.mts.map +1 -0
  132. package/dist/runtime.mjs +42 -0
  133. package/dist/runtime.mjs.map +1 -0
  134. package/dist/search-DG603UrT.mjs +9211 -0
  135. package/dist/search-DG603UrT.mjs.map +1 -0
  136. package/dist/seed/index.d.mts +3 -0
  137. package/dist/seed/index.mjs +15 -0
  138. package/dist/seo/index.d.mts +70 -0
  139. package/dist/seo/index.d.mts.map +1 -0
  140. package/dist/seo/index.mjs +70 -0
  141. package/dist/seo/index.mjs.map +1 -0
  142. package/dist/storage/local.d.mts +39 -0
  143. package/dist/storage/local.d.mts.map +1 -0
  144. package/dist/storage/local.mjs +166 -0
  145. package/dist/storage/local.mjs.map +1 -0
  146. package/dist/storage/s3.d.mts +32 -0
  147. package/dist/storage/s3.d.mts.map +1 -0
  148. package/dist/storage/s3.mjs +175 -0
  149. package/dist/storage/s3.mjs.map +1 -0
  150. package/dist/tokens-DpgrkrXK.mjs +171 -0
  151. package/dist/tokens-DpgrkrXK.mjs.map +1 -0
  152. package/dist/transport-BFGblqwG.d.mts +42 -0
  153. package/dist/transport-BFGblqwG.d.mts.map +1 -0
  154. package/dist/transport-yxiQsi8I.mjs +418 -0
  155. package/dist/transport-yxiQsi8I.mjs.map +1 -0
  156. package/dist/types-BRuPJGdV.d.mts +102 -0
  157. package/dist/types-BRuPJGdV.d.mts.map +1 -0
  158. package/dist/types-C4-fAxN3.d.mts +182 -0
  159. package/dist/types-C4-fAxN3.d.mts.map +1 -0
  160. package/dist/types-CMMN0pNg.mjs +31 -0
  161. package/dist/types-CMMN0pNg.mjs.map +1 -0
  162. package/dist/types-CUBbjgmP.mjs +16 -0
  163. package/dist/types-CUBbjgmP.mjs.map +1 -0
  164. package/dist/types-DRjfYOEv.d.mts +426 -0
  165. package/dist/types-DRjfYOEv.d.mts.map +1 -0
  166. package/dist/types-DY5zk5HN.mjs +73 -0
  167. package/dist/types-DY5zk5HN.mjs.map +1 -0
  168. package/dist/types-DaNLHo_T.d.mts +184 -0
  169. package/dist/types-DaNLHo_T.d.mts.map +1 -0
  170. package/dist/types-DvhsUmSJ.d.mts +1111 -0
  171. package/dist/types-DvhsUmSJ.d.mts.map +1 -0
  172. package/dist/validate-CpBtVMsD.d.mts +378 -0
  173. package/dist/validate-CpBtVMsD.d.mts.map +1 -0
  174. package/dist/validate-CqRJb_xU.mjs +97 -0
  175. package/dist/validate-CqRJb_xU.mjs.map +1 -0
  176. package/dist/validate-O7PWmlnq.mjs +328 -0
  177. package/dist/validate-O7PWmlnq.mjs.map +1 -0
  178. package/locals.d.ts +46 -0
  179. package/package.json +233 -19
  180. package/src/api/authorize.ts +63 -0
  181. package/src/api/csrf.ts +48 -0
  182. package/src/api/error.ts +99 -0
  183. package/src/api/errors.ts +445 -0
  184. package/src/api/escape.ts +9 -0
  185. package/src/api/handlers/api-tokens.ts +240 -0
  186. package/src/api/handlers/comments.ts +314 -0
  187. package/src/api/handlers/content.ts +1315 -0
  188. package/src/api/handlers/dashboard.ts +205 -0
  189. package/src/api/handlers/device-flow.ts +687 -0
  190. package/src/api/handlers/index.ts +163 -0
  191. package/src/api/handlers/manifest.ts +158 -0
  192. package/src/api/handlers/marketplace.ts +930 -0
  193. package/src/api/handlers/media.ts +207 -0
  194. package/src/api/handlers/menus.ts +493 -0
  195. package/src/api/handlers/oauth-authorization.ts +429 -0
  196. package/src/api/handlers/oauth-clients.ts +353 -0
  197. package/src/api/handlers/oauth-user-lookup.ts +39 -0
  198. package/src/api/handlers/plugins.ts +254 -0
  199. package/src/api/handlers/redirects.ts +360 -0
  200. package/src/api/handlers/revision.ts +145 -0
  201. package/src/api/handlers/schema.ts +534 -0
  202. package/src/api/handlers/sections.ts +289 -0
  203. package/src/api/handlers/seo.ts +115 -0
  204. package/src/api/handlers/settings.ts +49 -0
  205. package/src/api/handlers/snapshot.ts +350 -0
  206. package/src/api/handlers/taxonomies.ts +523 -0
  207. package/src/api/index.ts +6 -0
  208. package/src/api/openapi/document.ts +2368 -0
  209. package/src/api/openapi/index.ts +1 -0
  210. package/src/api/parse.ts +139 -0
  211. package/src/api/redirect.ts +14 -0
  212. package/src/api/rev.ts +67 -0
  213. package/src/api/schemas/auth.ts +112 -0
  214. package/src/api/schemas/bylines.ts +85 -0
  215. package/src/api/schemas/comments.ts +117 -0
  216. package/src/api/schemas/common.ts +89 -0
  217. package/src/api/schemas/content.ts +191 -0
  218. package/src/api/schemas/import.ts +52 -0
  219. package/src/api/schemas/index.ts +17 -0
  220. package/src/api/schemas/media.ts +116 -0
  221. package/src/api/schemas/menus.ts +111 -0
  222. package/src/api/schemas/redirects.ts +155 -0
  223. package/src/api/schemas/schema.ts +203 -0
  224. package/src/api/schemas/search.ts +63 -0
  225. package/src/api/schemas/sections.ts +67 -0
  226. package/src/api/schemas/settings.ts +63 -0
  227. package/src/api/schemas/setup.ts +37 -0
  228. package/src/api/schemas/taxonomies.ts +113 -0
  229. package/src/api/schemas/users.ts +96 -0
  230. package/src/api/schemas/widgets.ts +80 -0
  231. package/src/api/site-url.ts +25 -0
  232. package/src/api/types.ts +82 -0
  233. package/src/astro/index.ts +27 -0
  234. package/src/astro/integration/index.ts +303 -0
  235. package/src/astro/integration/routes.ts +834 -0
  236. package/src/astro/integration/runtime.ts +338 -0
  237. package/src/astro/integration/virtual-modules.ts +469 -0
  238. package/src/astro/integration/vite-config.ts +328 -0
  239. package/src/astro/middleware/auth.ts +743 -0
  240. package/src/astro/middleware/redirect.ts +89 -0
  241. package/src/astro/middleware/request-context.ts +129 -0
  242. package/src/astro/middleware/setup.ts +89 -0
  243. package/src/astro/middleware.ts +398 -0
  244. package/src/astro/routes/PluginRegistry.tsx +15 -0
  245. package/src/astro/routes/admin.astro +81 -0
  246. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  247. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  248. package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
  249. package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
  250. package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
  251. package/src/astro/routes/api/admin/bylines/index.ts +72 -0
  252. package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
  253. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  254. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  255. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  256. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  257. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
  258. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  259. package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
  260. package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
  261. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
  262. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
  263. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  264. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
  265. package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
  266. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  267. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
  268. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  269. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
  270. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  271. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  272. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  273. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
  274. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  275. package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
  276. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  277. package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
  278. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  279. package/src/astro/routes/api/admin/users/index.ts +66 -0
  280. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  281. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  282. package/src/astro/routes/api/auth/invite/complete.ts +84 -0
  283. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  284. package/src/astro/routes/api/auth/logout.ts +40 -0
  285. package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
  286. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  287. package/src/astro/routes/api/auth/me.ts +60 -0
  288. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
  289. package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
  290. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  291. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  292. package/src/astro/routes/api/auth/passkey/options.ts +82 -0
  293. package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
  294. package/src/astro/routes/api/auth/passkey/register/verify.ts +117 -0
  295. package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
  296. package/src/astro/routes/api/auth/signup/complete.ts +85 -0
  297. package/src/astro/routes/api/auth/signup/request.ts +77 -0
  298. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  299. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
  300. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  301. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
  302. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
  303. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
  304. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  305. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
  306. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
  307. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  308. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +105 -0
  309. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
  310. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
  311. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
  312. package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
  313. package/src/astro/routes/api/content/[collection]/index.ts +59 -0
  314. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  315. package/src/astro/routes/api/dashboard.ts +32 -0
  316. package/src/astro/routes/api/dev/emails.ts +36 -0
  317. package/src/astro/routes/api/import/probe.ts +47 -0
  318. package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
  319. package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
  320. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  321. package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
  322. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
  323. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  324. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  325. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
  326. package/src/astro/routes/api/manifest.ts +62 -0
  327. package/src/astro/routes/api/mcp.ts +124 -0
  328. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  329. package/src/astro/routes/api/media/[id].ts +145 -0
  330. package/src/astro/routes/api/media/file/[key].ts +79 -0
  331. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
  332. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  333. package/src/astro/routes/api/media/providers/index.ts +30 -0
  334. package/src/astro/routes/api/media/upload-url.ts +137 -0
  335. package/src/astro/routes/api/media.ts +190 -0
  336. package/src/astro/routes/api/menus/[name]/items.ts +87 -0
  337. package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
  338. package/src/astro/routes/api/menus/[name].ts +65 -0
  339. package/src/astro/routes/api/menus/index.ts +47 -0
  340. package/src/astro/routes/api/oauth/authorize.ts +412 -0
  341. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  342. package/src/astro/routes/api/oauth/device/code.ts +51 -0
  343. package/src/astro/routes/api/oauth/device/token.ts +69 -0
  344. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  345. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  346. package/src/astro/routes/api/oauth/token.ts +184 -0
  347. package/src/astro/routes/api/openapi.json.ts +32 -0
  348. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
  349. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  350. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  351. package/src/astro/routes/api/redirects/[id].ts +84 -0
  352. package/src/astro/routes/api/redirects/index.ts +52 -0
  353. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  354. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
  355. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
  356. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
  357. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
  358. package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
  359. package/src/astro/routes/api/schema/collections/index.ts +47 -0
  360. package/src/astro/routes/api/schema/index.ts +109 -0
  361. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  362. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  363. package/src/astro/routes/api/search/enable.ts +64 -0
  364. package/src/astro/routes/api/search/index.ts +55 -0
  365. package/src/astro/routes/api/search/rebuild.ts +72 -0
  366. package/src/astro/routes/api/search/stats.ts +35 -0
  367. package/src/astro/routes/api/search/suggest.ts +53 -0
  368. package/src/astro/routes/api/sections/[slug].ts +84 -0
  369. package/src/astro/routes/api/sections/index.ts +52 -0
  370. package/src/astro/routes/api/settings/email.ts +150 -0
  371. package/src/astro/routes/api/settings.ts +67 -0
  372. package/src/astro/routes/api/setup/admin-verify.ts +100 -0
  373. package/src/astro/routes/api/setup/admin.ts +94 -0
  374. package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
  375. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  376. package/src/astro/routes/api/setup/index.ts +126 -0
  377. package/src/astro/routes/api/setup/status.ts +122 -0
  378. package/src/astro/routes/api/snapshot.ts +75 -0
  379. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
  380. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
  381. package/src/astro/routes/api/taxonomies/index.ts +59 -0
  382. package/src/astro/routes/api/themes/preview.ts +77 -0
  383. package/src/astro/routes/api/typegen.ts +114 -0
  384. package/src/astro/routes/api/well-known/auth.ts +68 -0
  385. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
  386. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
  387. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +72 -0
  388. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
  389. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
  390. package/src/astro/routes/api/widget-areas/[name].ts +87 -0
  391. package/src/astro/routes/api/widget-areas/index.ts +99 -0
  392. package/src/astro/routes/api/widget-components.ts +22 -0
  393. package/src/astro/routes/robots.txt.ts +77 -0
  394. package/src/astro/routes/sitemap.xml.ts +97 -0
  395. package/src/astro/storage/adapters.ts +74 -0
  396. package/src/astro/storage/index.ts +19 -0
  397. package/src/astro/storage/types.ts +60 -0
  398. package/src/astro/types.ts +346 -0
  399. package/src/auth/api-tokens.ts +25 -0
  400. package/src/auth/challenge-store.ts +80 -0
  401. package/src/auth/mode.ts +96 -0
  402. package/src/auth/oauth-state-store.ts +96 -0
  403. package/src/auth/passkey-config.ts +27 -0
  404. package/src/auth/rate-limit.ts +158 -0
  405. package/src/auth/scopes.ts +33 -0
  406. package/src/auth/types.ts +104 -0
  407. package/src/aws-sdk.d.ts +100 -0
  408. package/src/bylines/index.ts +237 -0
  409. package/src/cleanup.ts +153 -0
  410. package/src/cli/client-factory.ts +100 -0
  411. package/src/cli/commands/auth.ts +46 -0
  412. package/src/cli/commands/bundle-utils.ts +247 -0
  413. package/src/cli/commands/bundle.ts +609 -0
  414. package/src/cli/commands/content.ts +442 -0
  415. package/src/cli/commands/dev.ts +191 -0
  416. package/src/cli/commands/doctor.ts +211 -0
  417. package/src/cli/commands/export-seed.ts +630 -0
  418. package/src/cli/commands/import/wordpress.ts +1056 -0
  419. package/src/cli/commands/init.ts +192 -0
  420. package/src/cli/commands/login.ts +547 -0
  421. package/src/cli/commands/media.ts +165 -0
  422. package/src/cli/commands/menu.ts +67 -0
  423. package/src/cli/commands/plugin-init.ts +291 -0
  424. package/src/cli/commands/plugin-validate.ts +31 -0
  425. package/src/cli/commands/plugin.ts +33 -0
  426. package/src/cli/commands/publish.ts +699 -0
  427. package/src/cli/commands/schema.ts +233 -0
  428. package/src/cli/commands/search-cmd.ts +54 -0
  429. package/src/cli/commands/seed.ts +288 -0
  430. package/src/cli/commands/taxonomy.ts +128 -0
  431. package/src/cli/commands/types.ts +68 -0
  432. package/src/cli/credentials.ts +236 -0
  433. package/src/cli/index.ts +70 -0
  434. package/src/cli/output.ts +75 -0
  435. package/src/cli/wxr/parser.ts +969 -0
  436. package/src/client/cf-access.ts +193 -0
  437. package/src/client/index.ts +854 -0
  438. package/src/client/portable-text.ts +413 -0
  439. package/src/client/transport.ts +200 -0
  440. package/src/comments/moderator.ts +46 -0
  441. package/src/comments/notifications.ts +144 -0
  442. package/src/comments/query.ts +105 -0
  443. package/src/comments/service.ts +213 -0
  444. package/src/components/Break.astro +45 -0
  445. package/src/components/Button.astro +71 -0
  446. package/src/components/Buttons.astro +49 -0
  447. package/src/components/Code.astro +59 -0
  448. package/src/components/Columns.astro +59 -0
  449. package/src/components/CommentForm.astro +315 -0
  450. package/src/components/Comments.astro +232 -0
  451. package/src/components/Cover.astro +128 -0
  452. package/src/components/EmDashBodyEnd.astro +32 -0
  453. package/src/components/EmDashBodyStart.astro +32 -0
  454. package/src/components/EmDashHead.astro +53 -0
  455. package/src/components/EmDashImage.astro +178 -0
  456. package/src/components/EmDashMedia.astro +167 -0
  457. package/src/components/Embed.astro +128 -0
  458. package/src/components/File.astro +122 -0
  459. package/src/components/Gallery.astro +93 -0
  460. package/src/components/HtmlBlock.astro +33 -0
  461. package/src/components/Image.astro +178 -0
  462. package/src/components/InlineEditor.astro +27 -0
  463. package/src/components/InlinePortableTextEditor.tsx +1905 -0
  464. package/src/components/LiveSearch.astro +614 -0
  465. package/src/components/PortableText.astro +51 -0
  466. package/src/components/Pullquote.astro +51 -0
  467. package/src/components/Table.astro +108 -0
  468. package/src/components/WidgetArea.astro +22 -0
  469. package/src/components/WidgetRenderer.astro +72 -0
  470. package/src/components/index.ts +116 -0
  471. package/src/components/marks/Link.astro +31 -0
  472. package/src/components/marks/StrikeThrough.astro +7 -0
  473. package/src/components/marks/Subscript.astro +7 -0
  474. package/src/components/marks/Superscript.astro +7 -0
  475. package/src/components/marks/Underline.astro +7 -0
  476. package/src/components/widgets/Archives.astro +65 -0
  477. package/src/components/widgets/Categories.astro +35 -0
  478. package/src/components/widgets/RecentPosts.astro +51 -0
  479. package/src/components/widgets/Search.astro +18 -0
  480. package/src/components/widgets/Tags.astro +38 -0
  481. package/src/content/converters/index.ts +9 -0
  482. package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
  483. package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
  484. package/src/content/converters/types.ts +120 -0
  485. package/src/content/index.ts +5 -0
  486. package/src/database/connection.ts +67 -0
  487. package/src/database/dialect-helpers.ts +138 -0
  488. package/src/database/index.ts +5 -0
  489. package/src/database/migrations/001_initial.ts +136 -0
  490. package/src/database/migrations/002_media_status.ts +26 -0
  491. package/src/database/migrations/003_schema_registry.ts +79 -0
  492. package/src/database/migrations/004_plugins.ts +62 -0
  493. package/src/database/migrations/005_menus.ts +67 -0
  494. package/src/database/migrations/006_taxonomy_defs.ts +51 -0
  495. package/src/database/migrations/007_widgets.ts +42 -0
  496. package/src/database/migrations/008_auth.ts +194 -0
  497. package/src/database/migrations/009_user_disabled.ts +27 -0
  498. package/src/database/migrations/011_sections.ts +65 -0
  499. package/src/database/migrations/012_search.ts +25 -0
  500. package/src/database/migrations/013_scheduled_publishing.ts +51 -0
  501. package/src/database/migrations/014_draft_revisions.ts +72 -0
  502. package/src/database/migrations/015_indexes.ts +82 -0
  503. package/src/database/migrations/016_api_tokens.ts +89 -0
  504. package/src/database/migrations/017_authorization_codes.ts +45 -0
  505. package/src/database/migrations/018_seo.ts +56 -0
  506. package/src/database/migrations/019_i18n.ts +618 -0
  507. package/src/database/migrations/020_collection_url_pattern.ts +23 -0
  508. package/src/database/migrations/021_remove_section_categories.ts +43 -0
  509. package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
  510. package/src/database/migrations/023_plugin_metadata.ts +33 -0
  511. package/src/database/migrations/024_media_placeholders.ts +32 -0
  512. package/src/database/migrations/025_oauth_clients.ts +28 -0
  513. package/src/database/migrations/026_cron_tasks.ts +49 -0
  514. package/src/database/migrations/027_comments.ts +87 -0
  515. package/src/database/migrations/028_drop_author_url.ts +9 -0
  516. package/src/database/migrations/029_redirects.ts +67 -0
  517. package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
  518. package/src/database/migrations/031_bylines.ts +90 -0
  519. package/src/database/migrations/032_rate_limits.ts +42 -0
  520. package/src/database/migrations/runner.ts +170 -0
  521. package/src/database/repositories/audit.ts +294 -0
  522. package/src/database/repositories/byline.ts +387 -0
  523. package/src/database/repositories/comment.ts +458 -0
  524. package/src/database/repositories/content.ts +1144 -0
  525. package/src/database/repositories/index.ts +30 -0
  526. package/src/database/repositories/media.ts +347 -0
  527. package/src/database/repositories/options.ts +150 -0
  528. package/src/database/repositories/plugin-storage.ts +373 -0
  529. package/src/database/repositories/redirect.ts +480 -0
  530. package/src/database/repositories/revision.ts +200 -0
  531. package/src/database/repositories/seo.ts +176 -0
  532. package/src/database/repositories/taxonomy.ts +294 -0
  533. package/src/database/repositories/types.ts +132 -0
  534. package/src/database/repositories/user.ts +258 -0
  535. package/src/database/transaction.ts +54 -0
  536. package/src/database/types.ts +501 -0
  537. package/src/database/validate.ts +138 -0
  538. package/src/db/adapters.ts +125 -0
  539. package/src/db/index.ts +37 -0
  540. package/src/db/libsql.ts +23 -0
  541. package/src/db/postgres.ts +30 -0
  542. package/src/db/sqlite.ts +27 -0
  543. package/src/emdash-runtime.ts +2096 -0
  544. package/src/fields/boolean.ts +34 -0
  545. package/src/fields/datetime.ts +44 -0
  546. package/src/fields/file.ts +41 -0
  547. package/src/fields/image.ts +34 -0
  548. package/src/fields/index.ts +42 -0
  549. package/src/fields/integer.ts +50 -0
  550. package/src/fields/json.ts +37 -0
  551. package/src/fields/multiselect.ts +48 -0
  552. package/src/fields/number.ts +52 -0
  553. package/src/fields/portable-text.ts +33 -0
  554. package/src/fields/reference.ts +29 -0
  555. package/src/fields/richtext.ts +31 -0
  556. package/src/fields/select.ts +46 -0
  557. package/src/fields/slug.ts +38 -0
  558. package/src/fields/text.ts +55 -0
  559. package/src/fields/textarea.ts +52 -0
  560. package/src/fields/types.ts +64 -0
  561. package/src/i18n/config.ts +68 -0
  562. package/src/import/index.ts +90 -0
  563. package/src/import/menus.ts +436 -0
  564. package/src/import/registry.ts +111 -0
  565. package/src/import/sections.ts +103 -0
  566. package/src/import/settings.ts +281 -0
  567. package/src/import/sources/wordpress-plugin.ts +641 -0
  568. package/src/import/sources/wordpress-rest.ts +191 -0
  569. package/src/import/sources/wxr.ts +330 -0
  570. package/src/import/ssrf.ts +260 -0
  571. package/src/import/types.ts +418 -0
  572. package/src/import/utils.ts +412 -0
  573. package/src/index.ts +481 -0
  574. package/src/loader.ts +770 -0
  575. package/src/mcp/server.ts +1463 -0
  576. package/src/media/index.ts +32 -0
  577. package/src/media/local-runtime.ts +213 -0
  578. package/src/media/local.ts +46 -0
  579. package/src/media/normalize.ts +190 -0
  580. package/src/media/placeholder.ts +150 -0
  581. package/src/media/provider-loader.ts +78 -0
  582. package/src/media/types.ts +279 -0
  583. package/src/menus/index.ts +324 -0
  584. package/src/menus/types.ts +112 -0
  585. package/src/page/context.ts +93 -0
  586. package/src/page/fragments.ts +89 -0
  587. package/src/page/index.ts +58 -0
  588. package/src/page/jsonld.ts +94 -0
  589. package/src/page/metadata.ts +185 -0
  590. package/src/page/seo-contributions.ts +136 -0
  591. package/src/plugin-utils.ts +80 -0
  592. package/src/plugins/adapt-sandbox-entry.ts +207 -0
  593. package/src/plugins/context.ts +833 -0
  594. package/src/plugins/cron.ts +361 -0
  595. package/src/plugins/define-plugin.ts +259 -0
  596. package/src/plugins/email-console.ts +73 -0
  597. package/src/plugins/email.ts +209 -0
  598. package/src/plugins/hooks.ts +1273 -0
  599. package/src/plugins/index.ts +193 -0
  600. package/src/plugins/manager.ts +595 -0
  601. package/src/plugins/manifest-schema.ts +230 -0
  602. package/src/plugins/marketplace.ts +460 -0
  603. package/src/plugins/request-meta.ts +139 -0
  604. package/src/plugins/routes.ts +302 -0
  605. package/src/plugins/sandbox/index.ts +18 -0
  606. package/src/plugins/sandbox/noop.ts +76 -0
  607. package/src/plugins/sandbox/types.ts +173 -0
  608. package/src/plugins/scheduler/node.ts +122 -0
  609. package/src/plugins/scheduler/piggyback.ts +71 -0
  610. package/src/plugins/scheduler/types.ts +27 -0
  611. package/src/plugins/state.ts +208 -0
  612. package/src/plugins/storage-indexes.ts +326 -0
  613. package/src/plugins/storage-query.ts +240 -0
  614. package/src/plugins/types.ts +1284 -0
  615. package/src/preview/helpers.ts +27 -0
  616. package/src/preview/index.ts +40 -0
  617. package/src/preview/tokens.ts +279 -0
  618. package/src/preview/urls.ts +118 -0
  619. package/src/query.ts +674 -0
  620. package/src/redirects/patterns.ts +224 -0
  621. package/src/request-context.ts +67 -0
  622. package/src/runtime.ts +21 -0
  623. package/src/schema/index.ts +29 -0
  624. package/src/schema/query.ts +44 -0
  625. package/src/schema/registry.ts +965 -0
  626. package/src/schema/types.ts +276 -0
  627. package/src/schema/zod-generator.ts +413 -0
  628. package/src/search/fts-manager.ts +452 -0
  629. package/src/search/index.ts +26 -0
  630. package/src/search/query.ts +396 -0
  631. package/src/search/text-extraction.ts +162 -0
  632. package/src/search/types.ts +114 -0
  633. package/src/sections/index.ts +226 -0
  634. package/src/sections/types.ts +86 -0
  635. package/src/seed/apply.ts +1141 -0
  636. package/src/seed/default.ts +86 -0
  637. package/src/seed/index.ts +28 -0
  638. package/src/seed/load.ts +35 -0
  639. package/src/seed/types.ts +341 -0
  640. package/src/seed/validate.ts +642 -0
  641. package/src/seo/index.ts +179 -0
  642. package/src/settings/index.ts +203 -0
  643. package/src/settings/types.ts +58 -0
  644. package/src/storage/index.ts +28 -0
  645. package/src/storage/local.ts +253 -0
  646. package/src/storage/s3.ts +271 -0
  647. package/src/storage/types.ts +204 -0
  648. package/src/taxonomies/index.ts +309 -0
  649. package/src/taxonomies/types.ts +61 -0
  650. package/src/ui.ts +75 -0
  651. package/src/utils/base64.ts +73 -0
  652. package/src/utils/hash.ts +36 -0
  653. package/src/utils/sanitize.ts +20 -0
  654. package/src/utils/slugify.ts +29 -0
  655. package/src/utils/url.ts +48 -0
  656. package/src/virtual-modules.d.ts +111 -0
  657. package/src/visual-editing/editable.ts +108 -0
  658. package/src/visual-editing/toolbar.ts +1229 -0
  659. package/src/widgets/components.ts +105 -0
  660. package/src/widgets/index.ts +131 -0
  661. package/src/widgets/types.ts +81 -0
@@ -0,0 +1,833 @@
1
+ /**
2
+ * Plugin Context v2
3
+ *
4
+ * Creates the unified context object provided to plugins in all hooks and routes.
5
+ *
6
+ */
7
+
8
+ import type { Kysely } from "kysely";
9
+ import { ulid } from "ulidx";
10
+
11
+ import { ContentRepository } from "../database/repositories/content.js";
12
+ import { MediaRepository } from "../database/repositories/media.js";
13
+ import { OptionsRepository } from "../database/repositories/options.js";
14
+ import { PluginStorageRepository } from "../database/repositories/plugin-storage.js";
15
+ import { UserRepository } from "../database/repositories/user.js";
16
+ import type { Database } from "../database/types.js";
17
+ import { validateExternalUrl, SsrfError, stripCredentialHeaders } from "../import/ssrf.js";
18
+ import type { Storage } from "../storage/types.js";
19
+ import { CronAccessImpl } from "./cron.js";
20
+ import type { EmailPipeline } from "./email.js";
21
+ import type {
22
+ ResolvedPlugin,
23
+ PluginContext,
24
+ PluginStorageConfig,
25
+ StorageCollection,
26
+ KVAccess,
27
+ CronAccess,
28
+ EmailAccess,
29
+ ContentAccess,
30
+ ContentAccessWithWrite,
31
+ MediaAccess,
32
+ MediaAccessWithWrite,
33
+ HttpAccess,
34
+ LogAccess,
35
+ SiteInfo,
36
+ UserAccess,
37
+ UserInfo,
38
+ ContentItem,
39
+ MediaItem,
40
+ PaginatedResult,
41
+ QueryOptions,
42
+ ContentListOptions,
43
+ MediaListOptions,
44
+ } from "./types.js";
45
+
46
+ // =============================================================================
47
+ // KV Access
48
+ // =============================================================================
49
+
50
+ /**
51
+ * Create KV accessor for a plugin
52
+ * All keys are automatically prefixed with the plugin ID
53
+ */
54
+ export function createKVAccess(optionsRepo: OptionsRepository, pluginId: string): KVAccess {
55
+ const prefix = `plugin:${pluginId}:`;
56
+
57
+ return {
58
+ async get<T>(key: string): Promise<T | null> {
59
+ return optionsRepo.get<T>(`${prefix}${key}`);
60
+ },
61
+
62
+ async set(key: string, value: unknown): Promise<void> {
63
+ await optionsRepo.set(`${prefix}${key}`, value);
64
+ },
65
+
66
+ async delete(key: string): Promise<boolean> {
67
+ return optionsRepo.delete(`${prefix}${key}`);
68
+ },
69
+
70
+ async list(keyPrefix?: string): Promise<Array<{ key: string; value: unknown }>> {
71
+ const fullPrefix = `${prefix}${keyPrefix ?? ""}`;
72
+ const entriesMap = await optionsRepo.getByPrefix(fullPrefix);
73
+ const result: Array<{ key: string; value: unknown }> = [];
74
+ for (const [fullKey, value] of entriesMap) {
75
+ result.push({
76
+ key: fullKey.slice(prefix.length),
77
+ value,
78
+ });
79
+ }
80
+ return result;
81
+ },
82
+ };
83
+ }
84
+
85
+ // =============================================================================
86
+ // Storage Access
87
+ // =============================================================================
88
+
89
+ /**
90
+ * Create storage collection accessor for a plugin
91
+ * Wraps PluginStorageRepository with the v2 interface (no async iterators)
92
+ */
93
+ function createStorageCollection<T>(
94
+ db: Kysely<Database>,
95
+ pluginId: string,
96
+ collectionName: string,
97
+ indexes: Array<string | string[]>,
98
+ ): StorageCollection<T> {
99
+ const repo = new PluginStorageRepository<T>(db, pluginId, collectionName, indexes);
100
+
101
+ return {
102
+ get: (id) => repo.get(id),
103
+ put: (id, data) => repo.put(id, data),
104
+ delete: (id) => repo.delete(id),
105
+ exists: (id) => repo.exists(id),
106
+ getMany: (ids) => repo.getMany(ids),
107
+ putMany: (items) => repo.putMany(items),
108
+ deleteMany: (ids) => repo.deleteMany(ids),
109
+ count: (where) => repo.count(where),
110
+
111
+ // Query returns PaginatedResult instead of the old format
112
+ async query(options?: QueryOptions): Promise<PaginatedResult<{ id: string; data: T }>> {
113
+ const result = await repo.query({
114
+ where: options?.where,
115
+ orderBy: options?.orderBy,
116
+ limit: options?.limit,
117
+ cursor: options?.cursor,
118
+ });
119
+
120
+ return {
121
+ items: result.items,
122
+ cursor: result.cursor,
123
+ hasMore: result.hasMore,
124
+ };
125
+ },
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Create storage accessor with all declared collections
131
+ */
132
+ export function createStorageAccess<T extends PluginStorageConfig>(
133
+ db: Kysely<Database>,
134
+ pluginId: string,
135
+ storageConfig: T,
136
+ ): Record<string, StorageCollection> {
137
+ const storage: Record<string, StorageCollection> = {};
138
+
139
+ for (const [collectionName, config] of Object.entries(storageConfig)) {
140
+ const allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];
141
+ storage[collectionName] = createStorageCollection(db, pluginId, collectionName, allIndexes);
142
+ }
143
+
144
+ return storage;
145
+ }
146
+
147
+ // =============================================================================
148
+ // Content Access
149
+ // =============================================================================
150
+
151
+ /**
152
+ * Create read-only content access
153
+ */
154
+ export function createContentAccess(db: Kysely<Database>): ContentAccess {
155
+ const contentRepo = new ContentRepository(db);
156
+
157
+ return {
158
+ async get(collection: string, id: string): Promise<ContentItem | null> {
159
+ const item = await contentRepo.findById(collection, id);
160
+ if (!item) return null;
161
+
162
+ return {
163
+ id: item.id,
164
+ type: item.type,
165
+ data: item.data,
166
+ createdAt: item.createdAt,
167
+ updatedAt: item.updatedAt,
168
+ };
169
+ },
170
+
171
+ async list(
172
+ collection: string,
173
+ options?: ContentListOptions,
174
+ ): Promise<PaginatedResult<ContentItem>> {
175
+ // Convert orderBy format if provided
176
+ let orderBy: { field: string; direction: "asc" | "desc" } | undefined;
177
+ if (options?.orderBy) {
178
+ const entries = Object.entries(options.orderBy);
179
+ const first = entries[0];
180
+ if (first) {
181
+ orderBy = { field: first[0], direction: first[1] };
182
+ }
183
+ }
184
+
185
+ const result = await contentRepo.findMany(collection, {
186
+ limit: options?.limit ?? 50,
187
+ cursor: options?.cursor,
188
+ orderBy,
189
+ });
190
+
191
+ return {
192
+ items: result.items.map((item) => ({
193
+ id: item.id,
194
+ type: item.type,
195
+ data: item.data,
196
+ createdAt: item.createdAt,
197
+ updatedAt: item.updatedAt,
198
+ })),
199
+ cursor: result.nextCursor,
200
+ hasMore: !!result.nextCursor,
201
+ };
202
+ },
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Create full content access with write operations
208
+ */
209
+ export function createContentAccessWithWrite(db: Kysely<Database>): ContentAccessWithWrite {
210
+ const contentRepo = new ContentRepository(db);
211
+ const readAccess = createContentAccess(db);
212
+
213
+ return {
214
+ ...readAccess,
215
+
216
+ async create(collection: string, data: Record<string, unknown>): Promise<ContentItem> {
217
+ const item = await contentRepo.create({
218
+ type: collection,
219
+ data,
220
+ });
221
+
222
+ return {
223
+ id: item.id,
224
+ type: item.type,
225
+ data: item.data,
226
+ createdAt: item.createdAt,
227
+ updatedAt: item.updatedAt,
228
+ };
229
+ },
230
+
231
+ async update(
232
+ collection: string,
233
+ id: string,
234
+ data: Record<string, unknown>,
235
+ ): Promise<ContentItem> {
236
+ const item = await contentRepo.update(collection, id, { data });
237
+
238
+ return {
239
+ id: item.id,
240
+ type: item.type,
241
+ data: item.data,
242
+ createdAt: item.createdAt,
243
+ updatedAt: item.updatedAt,
244
+ };
245
+ },
246
+
247
+ async delete(collection: string, id: string): Promise<boolean> {
248
+ return contentRepo.delete(collection, id);
249
+ },
250
+ };
251
+ }
252
+
253
+ // =============================================================================
254
+ // Media Access
255
+ // =============================================================================
256
+
257
+ /**
258
+ * Create read-only media access
259
+ */
260
+ export function createMediaAccess(db: Kysely<Database>): MediaAccess {
261
+ const mediaRepo = new MediaRepository(db);
262
+
263
+ return {
264
+ async get(id: string): Promise<MediaItem | null> {
265
+ const item = await mediaRepo.findById(id);
266
+ if (!item) return null;
267
+
268
+ return {
269
+ id: item.id,
270
+ filename: item.filename,
271
+ mimeType: item.mimeType,
272
+ size: item.size,
273
+ // Construct URL from storage key (or use a sensible default path)
274
+ url: `/media/${item.id}/${item.filename}`,
275
+ createdAt: item.createdAt,
276
+ };
277
+ },
278
+
279
+ async list(options?: MediaListOptions): Promise<PaginatedResult<MediaItem>> {
280
+ const result = await mediaRepo.findMany({
281
+ limit: options?.limit ?? 50,
282
+ cursor: options?.cursor,
283
+ mimeType: options?.mimeType,
284
+ });
285
+
286
+ return {
287
+ items: result.items.map((item) => ({
288
+ id: item.id,
289
+ filename: item.filename,
290
+ mimeType: item.mimeType,
291
+ size: item.size,
292
+ url: `/media/${item.id}/${item.filename}`,
293
+ createdAt: item.createdAt,
294
+ })),
295
+ cursor: result.nextCursor,
296
+ hasMore: !!result.nextCursor,
297
+ };
298
+ },
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Create full media access with write operations.
304
+ * If storage is not provided, upload() will throw at call time.
305
+ */
306
+ export function createMediaAccessWithWrite(
307
+ db: Kysely<Database>,
308
+ getUploadUrlFn: (
309
+ filename: string,
310
+ contentType: string,
311
+ ) => Promise<{ uploadUrl: string; mediaId: string }>,
312
+ storage?: Storage,
313
+ ): MediaAccessWithWrite {
314
+ const mediaRepo = new MediaRepository(db);
315
+ const readAccess = createMediaAccess(db);
316
+
317
+ return {
318
+ ...readAccess,
319
+
320
+ getUploadUrl: getUploadUrlFn,
321
+
322
+ async upload(
323
+ filename: string,
324
+ contentType: string,
325
+ bytes: ArrayBuffer,
326
+ ): Promise<{ mediaId: string; storageKey: string; url: string }> {
327
+ if (!storage) {
328
+ throw new Error(
329
+ "Media upload() requires a storage backend. Configure storage in PluginContextFactoryOptions.",
330
+ );
331
+ }
332
+
333
+ const mediaId = ulid();
334
+ // Extract extension from basename (ignore path separators)
335
+ const basename = filename.split("/").pop() ?? filename;
336
+ const dotIdx = basename.lastIndexOf(".");
337
+ const ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : "";
338
+ const storageKey = `${mediaId}${ext}`;
339
+
340
+ // Upload to storage first
341
+ await storage.upload({
342
+ key: storageKey,
343
+ body: new Uint8Array(bytes),
344
+ contentType,
345
+ });
346
+
347
+ // Create DB record — clean up storage on failure
348
+ try {
349
+ await mediaRepo.create({
350
+ filename: basename,
351
+ mimeType: contentType,
352
+ size: bytes.byteLength,
353
+ storageKey,
354
+ status: "ready",
355
+ });
356
+ } catch (error) {
357
+ try {
358
+ await storage.delete(storageKey);
359
+ } catch {
360
+ // Best-effort cleanup
361
+ }
362
+ throw error;
363
+ }
364
+
365
+ return {
366
+ mediaId,
367
+ storageKey,
368
+ url: `/_emdash/api/media/file/${storageKey}`,
369
+ };
370
+ },
371
+
372
+ async delete(id: string): Promise<boolean> {
373
+ return mediaRepo.delete(id);
374
+ },
375
+ };
376
+ }
377
+
378
+ // =============================================================================
379
+ // HTTP Access
380
+ // =============================================================================
381
+
382
+ /** Maximum number of redirects to follow in plugin HTTP access */
383
+ const MAX_PLUGIN_REDIRECTS = 5;
384
+
385
+ function isHostAllowed(host: string, allowedHosts: string[]): boolean {
386
+ return allowedHosts.some((pattern) => {
387
+ if (pattern.startsWith("*.")) {
388
+ const suffix = pattern.slice(1); // ".example.com"
389
+ return host.endsWith(suffix) || host === pattern.slice(2);
390
+ }
391
+ return host === pattern;
392
+ });
393
+ }
394
+
395
+ /**
396
+ * Create HTTP access with host validation.
397
+ *
398
+ * Uses redirect: "manual" to re-validate each redirect target against
399
+ * the allowedHosts list, preventing redirects to unauthorized hosts.
400
+ */
401
+ export function createHttpAccess(pluginId: string, allowedHosts: string[]): HttpAccess {
402
+ return {
403
+ async fetch(url: string, init?: RequestInit): Promise<Response> {
404
+ // Deny by default — plugins must declare allowed hosts
405
+ if (allowedHosts.length === 0) {
406
+ throw new Error(
407
+ `Plugin "${pluginId}" has no allowed hosts configured. ` +
408
+ `Add hosts to the plugin's allowedHosts array to enable HTTP requests.`,
409
+ );
410
+ }
411
+
412
+ let currentUrl = url;
413
+ let currentInit = init;
414
+
415
+ for (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {
416
+ const hostname = new URL(currentUrl).hostname;
417
+ if (!isHostAllowed(hostname, allowedHosts)) {
418
+ throw new Error(
419
+ `Plugin "${pluginId}" is not allowed to fetch from host "${hostname}". ` +
420
+ `Allowed hosts: ${allowedHosts.join(", ")}`,
421
+ );
422
+ }
423
+
424
+ const response = await globalThis.fetch(currentUrl, {
425
+ ...currentInit,
426
+ redirect: "manual",
427
+ });
428
+
429
+ // Not a redirect -- return directly
430
+ if (response.status < 300 || response.status >= 400) {
431
+ return response;
432
+ }
433
+
434
+ // Extract redirect target
435
+ const location = response.headers.get("Location");
436
+ if (!location) {
437
+ return response;
438
+ }
439
+
440
+ // Resolve relative redirects; strip credentials on cross-origin hops
441
+ const previousOrigin = new URL(currentUrl).origin;
442
+ currentUrl = new URL(location, currentUrl).href;
443
+ const nextOrigin = new URL(currentUrl).origin;
444
+
445
+ if (previousOrigin !== nextOrigin && currentInit) {
446
+ currentInit = stripCredentialHeaders(currentInit);
447
+ }
448
+ }
449
+
450
+ throw new Error(`Plugin "${pluginId}": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);
451
+ },
452
+ };
453
+ }
454
+
455
+ /**
456
+ * Create unrestricted HTTP access (for plugins with network:fetch:any capability).
457
+ * No host validation, but applies SSRF protection on redirect targets to
458
+ * prevent plugins from being tricked into reaching internal services.
459
+ */
460
+ export function createUnrestrictedHttpAccess(pluginId: string): HttpAccess {
461
+ return {
462
+ async fetch(url: string, init?: RequestInit): Promise<Response> {
463
+ let currentUrl = url;
464
+ let currentInit = init;
465
+
466
+ for (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {
467
+ // Validate each URL against SSRF rules (private IPs, metadata endpoints)
468
+ try {
469
+ validateExternalUrl(currentUrl);
470
+ } catch (e) {
471
+ const msg = e instanceof SsrfError ? e.message : "SSRF validation failed";
472
+ throw new Error(
473
+ `Plugin "${pluginId}": blocked fetch to "${new URL(currentUrl).hostname}": ${msg}`,
474
+ { cause: e },
475
+ );
476
+ }
477
+
478
+ const response = await globalThis.fetch(currentUrl, {
479
+ ...currentInit,
480
+ redirect: "manual",
481
+ });
482
+
483
+ // Not a redirect -- return directly
484
+ if (response.status < 300 || response.status >= 400) {
485
+ return response;
486
+ }
487
+
488
+ // Extract redirect target
489
+ const location = response.headers.get("Location");
490
+ if (!location) {
491
+ return response;
492
+ }
493
+
494
+ // Resolve relative redirects; strip credentials on cross-origin hops
495
+ const previousOrigin = new URL(currentUrl).origin;
496
+ currentUrl = new URL(location, currentUrl).href;
497
+ const nextOrigin = new URL(currentUrl).origin;
498
+
499
+ if (previousOrigin !== nextOrigin && currentInit) {
500
+ currentInit = stripCredentialHeaders(currentInit);
501
+ }
502
+ }
503
+
504
+ throw new Error(`Plugin "${pluginId}": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);
505
+ },
506
+ };
507
+ }
508
+
509
+ /**
510
+ * Create blocked HTTP access (for plugins without network:fetch capability)
511
+ */
512
+ export function createBlockedHttpAccess(pluginId: string): HttpAccess {
513
+ return {
514
+ async fetch(): Promise<never> {
515
+ throw new Error(
516
+ `Plugin "${pluginId}" does not have the "network:fetch" capability. ` +
517
+ `Add "network:fetch" to the plugin's capabilities to enable HTTP requests.`,
518
+ );
519
+ },
520
+ };
521
+ }
522
+
523
+ // =============================================================================
524
+ // Log Access
525
+ // =============================================================================
526
+
527
+ /**
528
+ * Create logger for a plugin
529
+ */
530
+ export function createLogAccess(pluginId: string): LogAccess {
531
+ const prefix = `[plugin:${pluginId}]`;
532
+
533
+ return {
534
+ debug(message: string, data?: unknown): void {
535
+ if (data !== undefined) {
536
+ console.debug(prefix, message, data);
537
+ } else {
538
+ console.debug(prefix, message);
539
+ }
540
+ },
541
+
542
+ info(message: string, data?: unknown): void {
543
+ if (data !== undefined) {
544
+ console.info(prefix, message, data);
545
+ } else {
546
+ console.info(prefix, message);
547
+ }
548
+ },
549
+
550
+ warn(message: string, data?: unknown): void {
551
+ if (data !== undefined) {
552
+ console.warn(prefix, message, data);
553
+ } else {
554
+ console.warn(prefix, message);
555
+ }
556
+ },
557
+
558
+ error(message: string, data?: unknown): void {
559
+ if (data !== undefined) {
560
+ console.error(prefix, message, data);
561
+ } else {
562
+ console.error(prefix, message);
563
+ }
564
+ },
565
+ };
566
+ }
567
+
568
+ // =============================================================================
569
+ // Site Info
570
+ // =============================================================================
571
+
572
+ const TRAILING_SLASH_RE = /\/$/;
573
+
574
+ /**
575
+ * Options for creating site info
576
+ */
577
+ export interface SiteInfoOptions {
578
+ /** Site name from options table */
579
+ siteName?: string;
580
+ /** Site URL from options table or Astro config */
581
+ siteUrl?: string;
582
+ /** Site locale from options table */
583
+ locale?: string;
584
+ }
585
+
586
+ /**
587
+ * Create site info from config and settings.
588
+ *
589
+ * Resolution order for URL:
590
+ * 1. options table (emdash:site_url)
591
+ * 2. Astro `site` config
592
+ * 3. fallback to empty string
593
+ */
594
+ export function createSiteInfo(options: SiteInfoOptions): SiteInfo {
595
+ return {
596
+ name: options.siteName ?? "",
597
+ url: (options.siteUrl ?? "").replace(TRAILING_SLASH_RE, ""), // strip trailing slash
598
+ locale: options.locale ?? "en",
599
+ };
600
+ }
601
+
602
+ /**
603
+ * Create a URL helper that generates absolute URLs from relative paths.
604
+ * Validates that path starts with "/" and rejects protocol-relative paths ("//").
605
+ */
606
+ export function createUrlHelper(siteUrl: string): (path: string) => string {
607
+ const base = siteUrl.replace(TRAILING_SLASH_RE, ""); // strip trailing slash
608
+
609
+ return (path: string): string => {
610
+ if (!path.startsWith("/")) {
611
+ throw new Error(`URL path must start with "/", got: "${path}"`);
612
+ }
613
+ if (path.startsWith("//")) {
614
+ throw new Error(`URL path must not be protocol-relative, got: "${path}"`);
615
+ }
616
+ return `${base}${path}`;
617
+ };
618
+ }
619
+
620
+ // =============================================================================
621
+ // User Access
622
+ // =============================================================================
623
+
624
+ /**
625
+ * Convert a UserRepository user to the plugin-facing UserInfo shape.
626
+ * Strips sensitive fields (avatarUrl, emailVerified, data).
627
+ */
628
+ function toUserInfo(user: {
629
+ id: string;
630
+ email: string;
631
+ name: string | null;
632
+ role: number;
633
+ createdAt: string;
634
+ }): UserInfo {
635
+ return {
636
+ id: user.id,
637
+ email: user.email,
638
+ name: user.name,
639
+ role: user.role,
640
+ createdAt: user.createdAt,
641
+ };
642
+ }
643
+
644
+ /**
645
+ * Create read-only user access for plugins.
646
+ * Excludes sensitive fields (password hashes, sessions, passkeys, avatar URL, data).
647
+ */
648
+ export function createUserAccess(db: Kysely<Database>): UserAccess {
649
+ const userRepo = new UserRepository(db);
650
+
651
+ return {
652
+ async get(id: string): Promise<UserInfo | null> {
653
+ const user = await userRepo.findById(id);
654
+ if (!user) return null;
655
+ return toUserInfo(user);
656
+ },
657
+
658
+ async getByEmail(email: string): Promise<UserInfo | null> {
659
+ const user = await userRepo.findByEmail(email);
660
+ if (!user) return null;
661
+ return toUserInfo(user);
662
+ },
663
+
664
+ async list(opts?: {
665
+ role?: number;
666
+ limit?: number;
667
+ cursor?: string;
668
+ }): Promise<{ items: UserInfo[]; nextCursor?: string }> {
669
+ const result = await userRepo.findMany({
670
+ role: opts?.role as 10 | 20 | 30 | 40 | 50 | undefined,
671
+ cursor: opts?.cursor,
672
+ limit: opts?.limit,
673
+ });
674
+
675
+ return {
676
+ items: result.items.map(toUserInfo),
677
+ nextCursor: result.nextCursor,
678
+ };
679
+ },
680
+ };
681
+ }
682
+
683
+ // =============================================================================
684
+ // Plugin Context Factory
685
+ // =============================================================================
686
+
687
+ export interface PluginContextFactoryOptions {
688
+ db: Kysely<Database>;
689
+ /**
690
+ * Storage backend for direct media uploads.
691
+ * If not provided, upload() will throw.
692
+ */
693
+ storage?: Storage;
694
+ /**
695
+ * Function to generate upload URLs for media.
696
+ * If not provided, media write operations will throw.
697
+ */
698
+ getUploadUrl?: (
699
+ filename: string,
700
+ contentType: string,
701
+ ) => Promise<{ uploadUrl: string; mediaId: string }>;
702
+ /**
703
+ * Site information for ctx.site and ctx.url().
704
+ * If not provided, site info will have empty defaults.
705
+ */
706
+ siteInfo?: SiteInfoOptions;
707
+ /**
708
+ * Callback to notify the cron scheduler that the next due time may have changed.
709
+ * If not provided, ctx.cron will not be available.
710
+ */
711
+ cronReschedule?: () => void;
712
+ /**
713
+ * Email pipeline instance for ctx.email.
714
+ * If not provided (or no provider configured), ctx.email will be undefined.
715
+ */
716
+ emailPipeline?: EmailPipeline;
717
+ }
718
+
719
+ /**
720
+ * Factory for creating plugin contexts
721
+ */
722
+ export class PluginContextFactory {
723
+ private optionsRepo: OptionsRepository;
724
+ private db: Kysely<Database>;
725
+ private storage?: Storage;
726
+ private getUploadUrl?: (
727
+ filename: string,
728
+ contentType: string,
729
+ ) => Promise<{ uploadUrl: string; mediaId: string }>;
730
+ private site: SiteInfo;
731
+ private urlHelper: (path: string) => string;
732
+ private cronReschedule?: () => void;
733
+ private emailPipeline?: EmailPipeline;
734
+
735
+ constructor(options: PluginContextFactoryOptions) {
736
+ this.db = options.db;
737
+ this.optionsRepo = new OptionsRepository(options.db);
738
+ this.storage = options.storage;
739
+ this.getUploadUrl = options.getUploadUrl;
740
+ this.site = createSiteInfo(options.siteInfo ?? {});
741
+ this.urlHelper = createUrlHelper(this.site.url);
742
+ this.cronReschedule = options.cronReschedule;
743
+ this.emailPipeline = options.emailPipeline;
744
+ }
745
+
746
+ /**
747
+ * Create the unified plugin context
748
+ */
749
+ createContext(plugin: ResolvedPlugin): PluginContext {
750
+ const capabilities = new Set(plugin.capabilities);
751
+
752
+ // Always available
753
+ const kv = createKVAccess(this.optionsRepo, plugin.id);
754
+ const log = createLogAccess(plugin.id);
755
+ const storage = createStorageAccess(this.db, plugin.id, plugin.storage);
756
+
757
+ // Capability-gated: content
758
+ let content: ContentAccess | ContentAccessWithWrite | undefined;
759
+ if (capabilities.has("write:content")) {
760
+ content = createContentAccessWithWrite(this.db);
761
+ } else if (capabilities.has("read:content")) {
762
+ content = createContentAccess(this.db);
763
+ }
764
+
765
+ // Capability-gated: media
766
+ let media: MediaAccess | MediaAccessWithWrite | undefined;
767
+ if (capabilities.has("write:media") && this.getUploadUrl) {
768
+ media = createMediaAccessWithWrite(this.db, this.getUploadUrl, this.storage);
769
+ } else if (capabilities.has("read:media")) {
770
+ media = createMediaAccess(this.db);
771
+ }
772
+
773
+ // Capability-gated: http
774
+ let http: HttpAccess | undefined;
775
+ if (capabilities.has("network:fetch:any")) {
776
+ http = createUnrestrictedHttpAccess(plugin.id);
777
+ } else if (capabilities.has("network:fetch")) {
778
+ http = createHttpAccess(plugin.id, plugin.allowedHosts);
779
+ }
780
+
781
+ // Capability-gated: users
782
+ let users: UserAccess | undefined;
783
+ if (capabilities.has("read:users")) {
784
+ users = createUserAccess(this.db);
785
+ }
786
+
787
+ // Cron access ��� always available (scoped to plugin), but only if
788
+ // the runtime provided a reschedule callback (i.e. cron is wired up).
789
+ let cron: CronAccess | undefined;
790
+ if (this.cronReschedule) {
791
+ cron = new CronAccessImpl(this.db, plugin.id, this.cronReschedule);
792
+ }
793
+
794
+ // Email access — requires email:send capability AND a configured provider
795
+ let email: EmailAccess | undefined;
796
+ if (capabilities.has("email:send") && this.emailPipeline?.isAvailable()) {
797
+ const pipeline = this.emailPipeline;
798
+ const pluginId = plugin.id;
799
+ email = {
800
+ send: (message) => pipeline.send(message, pluginId),
801
+ };
802
+ }
803
+
804
+ return {
805
+ plugin: {
806
+ id: plugin.id,
807
+ version: plugin.version,
808
+ },
809
+ storage,
810
+ kv,
811
+ content,
812
+ media,
813
+ http,
814
+ log,
815
+ site: this.site,
816
+ url: this.urlHelper,
817
+ users,
818
+ cron,
819
+ email,
820
+ };
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Create a plugin context for a resolved plugin
826
+ */
827
+ export function createPluginContext(
828
+ options: PluginContextFactoryOptions,
829
+ plugin: ResolvedPlugin,
830
+ ): PluginContext {
831
+ const factory = new PluginContextFactory(options);
832
+ return factory.createContext(plugin);
833
+ }