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,361 @@
1
+ /**
2
+ * Plugin Cron System
3
+ *
4
+ * Provides scheduled task execution for plugins:
5
+ * - CronExecutor: claims overdue tasks, invokes per-plugin cron hook, updates next run.
6
+ * - CronAccessImpl: per-plugin API for schedule/cancel/list.
7
+ *
8
+ */
9
+
10
+ import { Cron } from "croner";
11
+ import type { Kysely } from "kysely";
12
+ import { sql } from "kysely";
13
+ import { ulid } from "ulidx";
14
+
15
+ import type { Database } from "../database/types.js";
16
+ import type { CronAccess, CronEvent, CronTaskInfo } from "./types.js";
17
+
18
+ /** Stale lock threshold in minutes */
19
+ const STALE_LOCK_MINUTES = 10;
20
+
21
+ /**
22
+ * Callback to invoke a plugin's cron hook.
23
+ * Provided by PluginManager so CronExecutor stays decoupled from the hook pipeline.
24
+ */
25
+ export type InvokeCronHookFn = (pluginId: string, event: CronEvent) => Promise<void>;
26
+
27
+ /**
28
+ * Callback to notify the scheduler that the next due time may have changed.
29
+ */
30
+ export type RescheduleFn = () => void;
31
+
32
+ // ─── CronExecutor ──────────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * Executes overdue cron tasks.
36
+ *
37
+ * Called by platform-specific schedulers (NodeCronScheduler, EmDashScheduler DO,
38
+ * PiggybackScheduler). Stateless — all state lives in the database.
39
+ */
40
+ export class CronExecutor {
41
+ constructor(
42
+ private db: Kysely<Database>,
43
+ private invokeCronHook: InvokeCronHookFn,
44
+ ) {}
45
+
46
+ /**
47
+ * Process all overdue tasks.
48
+ *
49
+ * 1. Atomically claim tasks whose next_run_at <= now, status = idle, enabled = 1.
50
+ * 2. For each claimed task, invoke the plugin's cron hook.
51
+ * 3. On success: compute next_run_at and reset to idle, or delete one-shots.
52
+ * 4. On failure: reset to idle (retry on next tick).
53
+ */
54
+ async tick(): Promise<number> {
55
+ const now = new Date().toISOString();
56
+ let processed = 0;
57
+
58
+ // Claim overdue tasks atomically
59
+ const claimed = await sql<{
60
+ id: string;
61
+ plugin_id: string;
62
+ task_name: string;
63
+ schedule: string;
64
+ is_oneshot: number;
65
+ data: string | null;
66
+ next_run_at: string;
67
+ }>`
68
+ UPDATE _emdash_cron_tasks
69
+ SET status = 'running', locked_at = ${now}
70
+ WHERE id IN (
71
+ SELECT id FROM _emdash_cron_tasks
72
+ WHERE next_run_at <= ${now}
73
+ AND status = 'idle'
74
+ AND enabled = 1
75
+ ORDER BY next_run_at ASC
76
+ LIMIT 10
77
+ )
78
+ RETURNING id, plugin_id, task_name, schedule, is_oneshot, data, next_run_at
79
+ `.execute(this.db);
80
+
81
+ for (const task of claimed.rows) {
82
+ // Parse task data safely ��� malformed JSON must not crash the entire batch
83
+ let parsedData: Record<string, unknown> | undefined;
84
+ if (task.data) {
85
+ try {
86
+ parsedData = JSON.parse(task.data) as Record<string, unknown>;
87
+ } catch {
88
+ console.error(
89
+ `[cron] Invalid JSON data for ${task.plugin_id}:${task.task_name}, skipping`,
90
+ );
91
+ await sql`
92
+ UPDATE _emdash_cron_tasks
93
+ SET status = 'idle', locked_at = NULL
94
+ WHERE id = ${task.id}
95
+ `.execute(this.db);
96
+ continue;
97
+ }
98
+ }
99
+
100
+ const event: CronEvent = {
101
+ name: task.task_name,
102
+ data: parsedData,
103
+ scheduledAt: task.next_run_at,
104
+ };
105
+
106
+ let hookFailed = false;
107
+ try {
108
+ await this.invokeCronHook(task.plugin_id, event);
109
+ } catch (error) {
110
+ hookFailed = true;
111
+ console.error(`[cron] Hook failed for ${task.plugin_id}:${task.task_name}:`, error);
112
+ }
113
+
114
+ if (task.is_oneshot) {
115
+ if (hookFailed) {
116
+ // Keep the task for retry — reset to idle with a 1-minute backoff
117
+ const retryAt = new Date(Date.now() + 60_000).toISOString();
118
+ await sql`
119
+ UPDATE _emdash_cron_tasks
120
+ SET status = 'idle', locked_at = NULL, next_run_at = ${retryAt}
121
+ WHERE id = ${task.id}
122
+ `.execute(this.db);
123
+ } else {
124
+ // Success: delete the one-shot task
125
+ await sql`
126
+ DELETE FROM _emdash_cron_tasks WHERE id = ${task.id}
127
+ `.execute(this.db);
128
+ }
129
+ } else {
130
+ // Recurring: compute next run and reset
131
+ const nextRun = nextCronTime(task.schedule);
132
+ await sql`
133
+ UPDATE _emdash_cron_tasks
134
+ SET status = 'idle',
135
+ locked_at = NULL,
136
+ last_run_at = ${now},
137
+ next_run_at = ${nextRun}
138
+ WHERE id = ${task.id}
139
+ `.execute(this.db);
140
+ }
141
+
142
+ processed++;
143
+ }
144
+
145
+ return processed;
146
+ }
147
+
148
+ /**
149
+ * Recover tasks stuck in 'running' for more than STALE_LOCK_MINUTES.
150
+ * These likely crashed mid-execution.
151
+ */
152
+ async recoverStaleLocks(): Promise<number> {
153
+ const cutoff = new Date(Date.now() - STALE_LOCK_MINUTES * 60 * 1000).toISOString();
154
+
155
+ const result = await sql`
156
+ UPDATE _emdash_cron_tasks
157
+ SET status = 'idle', locked_at = NULL
158
+ WHERE status = 'running'
159
+ AND locked_at < ${cutoff}
160
+ `.execute(this.db);
161
+
162
+ return Number(result.numAffectedRows ?? 0);
163
+ }
164
+
165
+ /**
166
+ * Get the next due time across all enabled tasks.
167
+ * Returns null if no tasks are scheduled.
168
+ */
169
+ async getNextDueTime(): Promise<string | null> {
170
+ const result = await sql<{ next: string | null }>`
171
+ SELECT MIN(next_run_at) as next
172
+ FROM _emdash_cron_tasks
173
+ WHERE status = 'idle' AND enabled = 1
174
+ `.execute(this.db);
175
+
176
+ return result.rows[0]?.next ?? null;
177
+ }
178
+ }
179
+
180
+ // ─── CronAccessImpl ────────────────────────────────────────────────────────
181
+
182
+ /**
183
+ * Per-plugin cron API implementation.
184
+ * Scoped to a single plugin ID — plugins cannot see or modify other plugins' tasks.
185
+ */
186
+ export class CronAccessImpl implements CronAccess {
187
+ constructor(
188
+ private db: Kysely<Database>,
189
+ private pluginId: string,
190
+ private reschedule: RescheduleFn,
191
+ ) {}
192
+
193
+ async schedule(
194
+ name: string,
195
+ opts: { schedule: string; data?: Record<string, unknown> },
196
+ ): Promise<void> {
197
+ validateTaskName(name);
198
+ validateSchedule(opts.schedule);
199
+
200
+ const oneshot = isOneShot(opts.schedule);
201
+ const nextRun = oneshot ? opts.schedule : nextCronTime(opts.schedule);
202
+ const dataJson = opts.data ? JSON.stringify(opts.data) : null;
203
+ const id = ulid();
204
+
205
+ // Upsert: if task already exists for this plugin+name, update it.
206
+ // Guard: don't clobber a task that is currently executing.
207
+ await sql`
208
+ INSERT INTO _emdash_cron_tasks (id, plugin_id, task_name, schedule, is_oneshot, data, next_run_at, status, enabled)
209
+ VALUES (${id}, ${this.pluginId}, ${name}, ${opts.schedule}, ${oneshot ? 1 : 0}, ${dataJson}, ${nextRun}, 'idle', 1)
210
+ ON CONFLICT (plugin_id, task_name) DO UPDATE SET
211
+ schedule = ${opts.schedule},
212
+ is_oneshot = ${oneshot ? 1 : 0},
213
+ data = ${dataJson},
214
+ next_run_at = ${nextRun},
215
+ status = CASE WHEN _emdash_cron_tasks.status = 'running' THEN 'running' ELSE 'idle' END,
216
+ locked_at = CASE WHEN _emdash_cron_tasks.status = 'running' THEN _emdash_cron_tasks.locked_at ELSE NULL END,
217
+ enabled = 1
218
+ `.execute(this.db);
219
+
220
+ this.reschedule();
221
+ }
222
+
223
+ async cancel(name: string): Promise<void> {
224
+ await sql`
225
+ DELETE FROM _emdash_cron_tasks
226
+ WHERE plugin_id = ${this.pluginId} AND task_name = ${name}
227
+ `.execute(this.db);
228
+
229
+ this.reschedule();
230
+ }
231
+
232
+ async list(): Promise<CronTaskInfo[]> {
233
+ const rows = await sql<{
234
+ task_name: string;
235
+ schedule: string;
236
+ next_run_at: string;
237
+ last_run_at: string | null;
238
+ }>`
239
+ SELECT task_name, schedule, next_run_at, last_run_at
240
+ FROM _emdash_cron_tasks
241
+ WHERE plugin_id = ${this.pluginId} AND enabled = 1
242
+ ORDER BY next_run_at ASC
243
+ `.execute(this.db);
244
+
245
+ return rows.rows.map((row) => ({
246
+ name: row.task_name,
247
+ schedule: row.schedule,
248
+ nextRunAt: row.next_run_at,
249
+ lastRunAt: row.last_run_at,
250
+ }));
251
+ }
252
+ }
253
+
254
+ // ─── Cron task lifecycle helpers ────────────────────────────────────────────
255
+
256
+ /**
257
+ * Enable or disable all cron tasks for a plugin.
258
+ * Called by admin disable/enable endpoints and PluginManager lifecycle.
259
+ * Gracefully handles the cron table not existing yet (pre-migration).
260
+ */
261
+ export async function setCronTasksEnabled(
262
+ db: Kysely<Database>,
263
+ pluginId: string,
264
+ enabled: boolean,
265
+ ): Promise<void> {
266
+ try {
267
+ await sql`
268
+ UPDATE _emdash_cron_tasks
269
+ SET enabled = ${enabled ? 1 : 0}
270
+ WHERE plugin_id = ${pluginId}
271
+ `.execute(db);
272
+ } catch {
273
+ // Cron table may not exist yet (pre-migration). Non-fatal.
274
+ }
275
+ }
276
+
277
+ // ─── Cron utilities ────────────────────────────────────────────────────────
278
+
279
+ /**
280
+ * Compute the next fire time for a cron expression.
281
+ * Supports standard cron (5-field), extended (6-field with seconds), and
282
+ * aliases like @daily, @weekly, @hourly, @monthly, @yearly.
283
+ */
284
+ export function nextCronTime(expression: string): string {
285
+ const job = new Cron(expression);
286
+ const next = job.nextRun();
287
+ if (!next) {
288
+ throw new Error(`Invalid cron expression or no future run: "${expression}"`);
289
+ }
290
+ return next.toISOString();
291
+ }
292
+
293
+ /**
294
+ * Check whether a string is a valid cron expression.
295
+ */
296
+ function isCronExpression(schedule: string): boolean {
297
+ try {
298
+ // Cron constructor validates; we discard the instance immediately.
299
+ const _cron = new Cron(schedule);
300
+ void _cron;
301
+ return true;
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Check if a schedule string is a one-shot (ISO 8601 datetime) rather than
309
+ * a recurring cron expression.
310
+ *
311
+ * Tries to parse as a cron expression first. Only if that fails does it
312
+ * attempt Date.parse. This avoids misclassifying cron range expressions
313
+ * like "1-5 * * * *" which Date.parse accepts as valid dates.
314
+ */
315
+ export function isOneShot(schedule: string): boolean {
316
+ if (schedule.startsWith("@")) return false;
317
+ if (isCronExpression(schedule)) return false;
318
+ return !isNaN(Date.parse(schedule));
319
+ }
320
+
321
+ /** Max length for a task name */
322
+ const MAX_TASK_NAME_LENGTH = 128;
323
+ /** Task name pattern: alphanumeric, dashes, underscores */
324
+ const TASK_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
325
+
326
+ /**
327
+ * Validate a cron task name.
328
+ * Must be non-empty, ≤128 chars, alphanumeric with dashes/underscores.
329
+ */
330
+ export function validateTaskName(name: string): void {
331
+ if (!name || name.length > MAX_TASK_NAME_LENGTH) {
332
+ throw new Error(
333
+ `Invalid task name: must be 1-${MAX_TASK_NAME_LENGTH} characters, got ${name.length}`,
334
+ );
335
+ }
336
+ if (!TASK_NAME_RE.test(name)) {
337
+ throw new Error(
338
+ `Invalid task name "${name}": must start with a letter and contain only letters, numbers, dashes, or underscores`,
339
+ );
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Validate a schedule string at registration time.
345
+ * Must be a valid cron expression or a parseable ISO 8601 datetime.
346
+ */
347
+ export function validateSchedule(schedule: string): void {
348
+ if (!schedule || schedule.length > 256) {
349
+ throw new Error(`Invalid schedule: must be 1-256 characters, got ${schedule.length}`);
350
+ }
351
+
352
+ // Try cron first
353
+ if (isCronExpression(schedule)) return;
354
+
355
+ const parsed = Date.parse(schedule);
356
+ if (isNaN(parsed)) {
357
+ throw new Error(
358
+ `Invalid schedule "${schedule}": must be a valid cron expression or ISO 8601 datetime`,
359
+ );
360
+ }
361
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * definePlugin() Helper
3
+ *
4
+ * Creates a properly typed and normalized plugin definition.
5
+ * Supports two formats:
6
+ *
7
+ * 1. **Native format** -- full PluginDefinition with id, version, capabilities, etc.
8
+ * Returns a ResolvedPlugin.
9
+ *
10
+ * 2. **Standard format** -- just { hooks, routes }. No id/version/capabilities.
11
+ * Returns the same object (identity function for type inference).
12
+ * Metadata comes from the descriptor at config time.
13
+ *
14
+ */
15
+
16
+ import type {
17
+ PluginDefinition,
18
+ ResolvedPlugin,
19
+ PluginHooks,
20
+ ResolvedPluginHooks,
21
+ ResolvedHook,
22
+ HookConfig,
23
+ PluginStorageConfig,
24
+ StandardPluginDefinition,
25
+ } from "./types.js";
26
+
27
+ // Plugin ID validation patterns
28
+ const SIMPLE_ID = /^[a-z0-9-]+$/;
29
+ const SCOPED_ID = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
30
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+/;
31
+
32
+ /**
33
+ * Define an EmDash plugin.
34
+ *
35
+ * **Standard format** -- the canonical format for plugins that work in both
36
+ * trusted and sandboxed modes. No id/version -- those come from the descriptor.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { definePlugin } from "emdash";
41
+ *
42
+ * export default definePlugin({
43
+ * hooks: {
44
+ * "content:afterSave": {
45
+ * handler: async (event, ctx) => {
46
+ * await ctx.kv.set("lastSave", Date.now());
47
+ * },
48
+ * },
49
+ * },
50
+ * routes: {
51
+ * status: {
52
+ * handler: async (routeCtx, ctx) => ({ ok: true }),
53
+ * },
54
+ * },
55
+ * });
56
+ * ```
57
+ *
58
+ * **Native format** -- for plugins that need React admin, direct DB access,
59
+ * or other capabilities not available in the sandbox.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { definePlugin } from "emdash";
64
+ *
65
+ * export default definePlugin({
66
+ * id: "my-plugin",
67
+ * version: "1.0.0",
68
+ * capabilities: ["read:content"],
69
+ * hooks: {
70
+ * "content:beforeSave": async (event, ctx) => {
71
+ * ctx.log.info("Saving content", { collection: event.collection });
72
+ * return event.content;
73
+ * }
74
+ * },
75
+ * routes: {
76
+ * "sync": {
77
+ * handler: async (ctx) => {
78
+ * return { status: "ok" };
79
+ * }
80
+ * }
81
+ * }
82
+ * });
83
+ * ```
84
+ */
85
+ // Native overload first -- PluginDefinition (with id+version) is more specific
86
+ export function definePlugin<TStorage extends PluginStorageConfig>(
87
+ definition: PluginDefinition<TStorage>,
88
+ ): ResolvedPlugin<TStorage>;
89
+ // Standard overload second -- catches { hooks, routes } without id/version
90
+ export function definePlugin(definition: StandardPluginDefinition): StandardPluginDefinition;
91
+ export function definePlugin<TStorage extends PluginStorageConfig>(
92
+ definition: PluginDefinition<TStorage> | StandardPluginDefinition,
93
+ ): ResolvedPlugin<TStorage> | StandardPluginDefinition {
94
+ // Standard format: has hooks/routes but no id/version
95
+ if (!("id" in definition) || !("version" in definition)) {
96
+ // Validate that the standard format has at least hooks or routes
97
+ if (!("hooks" in definition) && !("routes" in definition)) {
98
+ throw new Error(
99
+ "Standard plugin format requires at least `hooks` or `routes`. " +
100
+ "For native format, provide `id` and `version`.",
101
+ );
102
+ }
103
+ // Identity function -- return as-is for type inference.
104
+ // The adapter (adaptSandboxEntry) will convert this to a ResolvedPlugin at build time.
105
+ return definition;
106
+ }
107
+
108
+ return defineNativePlugin(definition);
109
+ }
110
+
111
+ /**
112
+ * Internal: define a native-format plugin with full validation and normalization.
113
+ */
114
+ function defineNativePlugin<TStorage extends PluginStorageConfig>(
115
+ definition: PluginDefinition<TStorage>,
116
+ ): ResolvedPlugin<TStorage> {
117
+ const {
118
+ id,
119
+ version,
120
+ capabilities = [],
121
+ allowedHosts = [],
122
+ hooks = {},
123
+ routes = {},
124
+ admin = {},
125
+ } = definition;
126
+
127
+ // Default to empty object if no storage declared.
128
+ // The empty object satisfies PluginStorageConfig (Record<string, ...>).
129
+ // The cast is structurally safe because an empty record has no keys to conflict.
130
+ const storage = (definition.storage ?? {}) as TStorage;
131
+
132
+ // Validate id format: either simple (my-plugin) or scoped (@scope/my-plugin)
133
+ // Simple: lowercase alphanumeric with dashes
134
+ // Scoped: @scope/name where both parts are lowercase alphanumeric with dashes
135
+ if (!SIMPLE_ID.test(id) && !SCOPED_ID.test(id)) {
136
+ throw new Error(
137
+ `Invalid plugin id "${id}". Must be lowercase alphanumeric with dashes (e.g., "my-plugin" or "@scope/my-plugin").`,
138
+ );
139
+ }
140
+
141
+ // Validate version format (basic semver)
142
+ if (!SEMVER_PATTERN.test(version)) {
143
+ throw new Error(`Invalid plugin version "${version}". Must be semver format (e.g., "1.0.0").`);
144
+ }
145
+
146
+ // Validate capabilities
147
+ const validCapabilities = new Set([
148
+ "network:fetch",
149
+ "network:fetch:any",
150
+ "read:content",
151
+ "write:content",
152
+ "read:media",
153
+ "write:media",
154
+ "read:users",
155
+ "email:send",
156
+ "email:provide",
157
+ "email:intercept",
158
+ "page:inject",
159
+ ]);
160
+ for (const cap of capabilities) {
161
+ if (!validCapabilities.has(cap)) {
162
+ throw new Error(`Invalid capability "${cap}" in plugin "${id}".`);
163
+ }
164
+ }
165
+
166
+ // Capability implications: broader capabilities imply narrower ones
167
+ const normalizedCapabilities = [...capabilities];
168
+ if (capabilities.includes("write:content") && !capabilities.includes("read:content")) {
169
+ normalizedCapabilities.push("read:content");
170
+ }
171
+ if (capabilities.includes("write:media") && !capabilities.includes("read:media")) {
172
+ normalizedCapabilities.push("read:media");
173
+ }
174
+ if (capabilities.includes("network:fetch:any") && !capabilities.includes("network:fetch")) {
175
+ normalizedCapabilities.push("network:fetch");
176
+ }
177
+
178
+ // Normalize hooks
179
+ const resolvedHooks = resolveHooks(hooks, id);
180
+
181
+ return {
182
+ id,
183
+ version,
184
+ capabilities: normalizedCapabilities,
185
+ allowedHosts,
186
+ storage,
187
+ hooks: resolvedHooks,
188
+ routes,
189
+ admin,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Resolve hooks to normalized format with defaults.
195
+ *
196
+ * PluginHooks and ResolvedPluginHooks share the same keys — each input value is
197
+ * `HookConfig<H> | H` and the output is `ResolvedHook<H>`. TS can't narrow
198
+ * the handler type through a dynamic key, so we assert at the loop boundary.
199
+ */
200
+ function resolveHooks(hooks: PluginHooks, pluginId: string): ResolvedPluginHooks {
201
+ const resolved: ResolvedPluginHooks = {};
202
+
203
+ for (const key of Object.keys(hooks) as Array<keyof PluginHooks>) {
204
+ const hook = hooks[key];
205
+ if (hook) {
206
+ (resolved as Record<string, unknown>)[key] = resolveHook(hook, pluginId);
207
+ }
208
+ }
209
+
210
+ return resolved;
211
+ }
212
+
213
+ /**
214
+ * Check if a hook value is a config object (has a `handler` property)
215
+ */
216
+ function isHookConfig<THandler>(
217
+ hook: HookConfig<THandler> | THandler,
218
+ ): hook is HookConfig<THandler> {
219
+ return typeof hook === "object" && hook !== null && "handler" in hook;
220
+ }
221
+
222
+ /**
223
+ * Resolve a single hook to normalized format
224
+ */
225
+ function resolveHook<THandler>(
226
+ hook: HookConfig<THandler> | THandler,
227
+ pluginId: string,
228
+ ): ResolvedHook<THandler> {
229
+ // If it's a config object with handler property
230
+ if (isHookConfig(hook)) {
231
+ if (hook.exclusive !== undefined && typeof hook.exclusive !== "boolean") {
232
+ throw new Error(
233
+ `Invalid "exclusive" value in hook config for plugin "${pluginId}". Must be boolean.`,
234
+ );
235
+ }
236
+ return {
237
+ priority: hook.priority ?? 100,
238
+ timeout: hook.timeout ?? 5000,
239
+ dependencies: hook.dependencies ?? [],
240
+ errorPolicy: hook.errorPolicy ?? "abort",
241
+ exclusive: hook.exclusive ?? false,
242
+ handler: hook.handler,
243
+ pluginId,
244
+ };
245
+ }
246
+
247
+ // It's just a handler function
248
+ return {
249
+ priority: 100,
250
+ timeout: 5000,
251
+ dependencies: [],
252
+ errorPolicy: "abort",
253
+ exclusive: false,
254
+ handler: hook,
255
+ pluginId,
256
+ };
257
+ }
258
+
259
+ export default definePlugin;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Dev Console Email Provider
3
+ *
4
+ * Built-in plugin that registers email:deliver as an exclusive hook.
5
+ * Logs emails to console and stores them in memory (capped at 100).
6
+ * Auto-activated when import.meta.env.DEV is true and no other provider is selected.
7
+ *
8
+ */
9
+
10
+ import type { EmailDeliverEvent, EmailMessage, PluginContext } from "./types.js";
11
+
12
+ /** Plugin ID for the dev console email provider */
13
+ export const DEV_CONSOLE_EMAIL_PLUGIN_ID = "emdash-console-email";
14
+
15
+ /** Maximum number of emails to keep in memory */
16
+ const MAX_STORED_EMAILS = 100;
17
+
18
+ /**
19
+ * Stored email record (in-memory only)
20
+ */
21
+ export interface StoredEmail {
22
+ message: EmailMessage;
23
+ source: string;
24
+ sentAt: string;
25
+ }
26
+
27
+ /** In-memory store for dev emails */
28
+ const storedEmails: StoredEmail[] = [];
29
+
30
+ /**
31
+ * Get all stored dev emails (most recent first).
32
+ */
33
+ export function getDevEmails(): StoredEmail[] {
34
+ return storedEmails.toReversed();
35
+ }
36
+
37
+ /**
38
+ * Clear all stored dev emails.
39
+ */
40
+ export function clearDevEmails(): void {
41
+ storedEmails.length = 0;
42
+ }
43
+
44
+ /**
45
+ * The email:deliver handler for the dev console provider.
46
+ * Logs to console and stores in memory.
47
+ */
48
+ export async function devConsoleEmailDeliver(
49
+ event: EmailDeliverEvent,
50
+ _ctx: PluginContext,
51
+ ): Promise<void> {
52
+ const { message, source } = event;
53
+
54
+ console.log(
55
+ `\nšŸ“§ [dev-email] Email sent\n` +
56
+ ` From: ${source}\n` +
57
+ ` To: ${message.to}\n` +
58
+ ` Subject: ${message.subject}\n` +
59
+ ` Text: ${message.text.slice(0, 200)}${message.text.length > 200 ? "..." : ""}\n`,
60
+ );
61
+
62
+ // Store the email
63
+ storedEmails.push({
64
+ message,
65
+ source,
66
+ sentAt: new Date().toISOString(),
67
+ });
68
+
69
+ // Cap at MAX_STORED_EMAILS
70
+ while (storedEmails.length > MAX_STORED_EMAILS) {
71
+ storedEmails.shift();
72
+ }
73
+ }