emdash 0.0.0-b → 0.0.2

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 -43
  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 +1336 -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-C0hCbYnD.mjs +1412 -0
  127. package/dist/runner-C0hCbYnD.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 +684 -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 +349 -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 +335 -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 +116 -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 +115 -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 +101 -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 +58 -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 +68 -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 +697 -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 +286 -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 +170 -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 +39 -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 +249 -0
  646. package/src/storage/s3.ts +263 -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,249 @@
1
+ /**
2
+ * Local Filesystem Storage Implementation
3
+ *
4
+ * For development and testing. Stores files in a local directory.
5
+ */
6
+
7
+ import { createReadStream, existsSync } from "node:fs";
8
+ import * as fs from "node:fs/promises";
9
+ import * as path from "node:path";
10
+ import { Readable } from "node:stream";
11
+
12
+ import mime from "mime/lite";
13
+
14
+ /** Type guard for Node.js ErrnoException */
15
+ function isNodeError(error: unknown): error is NodeJS.ErrnoException {
16
+ return error instanceof Error && "code" in error;
17
+ }
18
+
19
+ import type {
20
+ Storage,
21
+ LocalStorageConfig,
22
+ UploadResult,
23
+ DownloadResult,
24
+ ListResult,
25
+ ListOptions,
26
+ SignedUploadUrl,
27
+ SignedUploadOptions,
28
+ } from "./types.js";
29
+ import { EmDashStorageError } from "./types.js";
30
+
31
+ /** Pattern to remove leading slashes */
32
+ const LEADING_SLASH_PATTERN = /^\//;
33
+
34
+ /** Pattern to remove trailing slashes */
35
+ const TRAILING_SLASH_PATTERN = /\/$/;
36
+
37
+ /**
38
+ * Local filesystem storage implementation
39
+ */
40
+ export class LocalStorage implements Storage {
41
+ /** Resolved absolute base directory for all stored files */
42
+ private directory: string;
43
+ private baseUrl: string;
44
+
45
+ constructor(config: LocalStorageConfig) {
46
+ this.directory = path.resolve(config.directory);
47
+ this.baseUrl = config.baseUrl.replace(TRAILING_SLASH_PATTERN, "");
48
+ }
49
+
50
+ /**
51
+ * Resolve a storage key to an absolute file path, ensuring it stays
52
+ * within the configured storage directory. Uses path.resolve() for
53
+ * canonical resolution rather than regex stripping.
54
+ *
55
+ * @throws EmDashStorageError if the resolved path escapes the base directory
56
+ */
57
+ private getFilePath(key: string): string {
58
+ const normalizedKey = key.replace(LEADING_SLASH_PATTERN, "");
59
+ const resolved = path.resolve(this.directory, normalizedKey);
60
+
61
+ // Verify the resolved path is within the base directory
62
+ if (!resolved.startsWith(this.directory + path.sep) && resolved !== this.directory) {
63
+ throw new EmDashStorageError("Invalid file path", "INVALID_PATH");
64
+ }
65
+
66
+ return resolved;
67
+ }
68
+
69
+ async upload(options: {
70
+ key: string;
71
+ body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
72
+ contentType: string;
73
+ }): Promise<UploadResult> {
74
+ try {
75
+ const filePath = this.getFilePath(options.key);
76
+ const dir = path.dirname(filePath);
77
+
78
+ // Ensure directory exists
79
+ await fs.mkdir(dir, { recursive: true });
80
+
81
+ // Convert body to buffer
82
+ let buffer: Buffer;
83
+ if (options.body instanceof ReadableStream) {
84
+ const chunks: Uint8Array[] = [];
85
+ const reader = options.body.getReader();
86
+ while (true) {
87
+ const { done, value } = await reader.read();
88
+ if (done) break;
89
+ chunks.push(value);
90
+ }
91
+ buffer = Buffer.concat(chunks);
92
+ } else if (options.body instanceof Uint8Array) {
93
+ buffer = Buffer.from(options.body);
94
+ } else {
95
+ buffer = options.body;
96
+ }
97
+
98
+ await fs.writeFile(filePath, buffer);
99
+
100
+ return {
101
+ key: options.key,
102
+ url: this.getPublicUrl(options.key),
103
+ size: buffer.length,
104
+ };
105
+ } catch (error) {
106
+ throw new EmDashStorageError(`Failed to upload file: ${options.key}`, "UPLOAD_FAILED", error);
107
+ }
108
+ }
109
+
110
+ async download(key: string): Promise<DownloadResult> {
111
+ try {
112
+ const filePath = this.getFilePath(key);
113
+
114
+ if (!existsSync(filePath)) {
115
+ throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND");
116
+ }
117
+
118
+ const stat = await fs.stat(filePath);
119
+ const nodeStream = createReadStream(filePath);
120
+
121
+ // Convert Node.js stream to web ReadableStream
122
+ // Readable.toWeb returns ReadableStream (which is ReadableStream<unknown>),
123
+ // but Node ReadStreams produce Buffer/Uint8Array chunks
124
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Readable.toWeb returns ReadableStream<unknown>; Node ReadStreams produce Uint8Array chunks
125
+ const webStream: ReadableStream<Uint8Array> = Readable.toWeb(
126
+ nodeStream,
127
+ ) as ReadableStream<Uint8Array>;
128
+
129
+ // Infer content type from extension
130
+ const ext = path.extname(key).toLowerCase();
131
+ const contentType = getContentType(ext);
132
+
133
+ return {
134
+ body: webStream,
135
+ contentType,
136
+ size: stat.size,
137
+ };
138
+ } catch (error) {
139
+ if (error instanceof EmDashStorageError) throw error;
140
+ throw new EmDashStorageError(`Failed to download file: ${key}`, "DOWNLOAD_FAILED", error);
141
+ }
142
+ }
143
+
144
+ async delete(key: string): Promise<void> {
145
+ try {
146
+ const filePath = this.getFilePath(key);
147
+ await fs.unlink(filePath);
148
+ } catch (error) {
149
+ // Ignore "file not found" errors (idempotent delete)
150
+ if (!isNodeError(error) || error.code !== "ENOENT") {
151
+ throw new EmDashStorageError(`Failed to delete file: ${key}`, "DELETE_FAILED", error);
152
+ }
153
+ }
154
+ }
155
+
156
+ async exists(key: string): Promise<boolean> {
157
+ try {
158
+ const filePath = this.getFilePath(key);
159
+ await fs.access(filePath);
160
+ return true;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+
166
+ async list(options: ListOptions = {}): Promise<ListResult> {
167
+ try {
168
+ const prefix = options.prefix || "";
169
+ const searchDir = path.resolve(this.directory, path.dirname(prefix));
170
+
171
+ // Validate the search directory stays within the base directory
172
+ if (!searchDir.startsWith(this.directory + path.sep) && searchDir !== this.directory) {
173
+ throw new EmDashStorageError("Invalid list prefix", "INVALID_PATH");
174
+ }
175
+
176
+ const prefixBase = path.basename(prefix);
177
+
178
+ // Ensure directory exists
179
+ try {
180
+ await fs.access(searchDir);
181
+ } catch {
182
+ return { files: [] };
183
+ }
184
+
185
+ const entries = await fs.readdir(searchDir, { withFileTypes: true });
186
+ const files: ListResult["files"] = [];
187
+
188
+ for (const entry of entries) {
189
+ if (entry.isFile() && entry.name.startsWith(prefixBase)) {
190
+ const key = path.join(path.dirname(prefix), entry.name);
191
+ const filePath = path.join(searchDir, entry.name);
192
+ const stat = await fs.stat(filePath);
193
+
194
+ files.push({
195
+ key,
196
+ size: stat.size,
197
+ lastModified: stat.mtime,
198
+ });
199
+ }
200
+ }
201
+
202
+ // Sort by last modified (newest first)
203
+ files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
204
+
205
+ // Apply limit and cursor (simple implementation)
206
+ const startIndex = options.cursor ? parseInt(options.cursor, 10) : 0;
207
+ const limit = options.limit || 1000;
208
+ const paginatedFiles = files.slice(startIndex, startIndex + limit);
209
+ const hasMore = startIndex + limit < files.length;
210
+
211
+ return {
212
+ files: paginatedFiles,
213
+ nextCursor: hasMore ? String(startIndex + limit) : undefined,
214
+ };
215
+ } catch (error) {
216
+ throw new EmDashStorageError("Failed to list files", "LIST_FAILED", error);
217
+ }
218
+ }
219
+
220
+ async getSignedUploadUrl(_options: SignedUploadOptions): Promise<SignedUploadUrl> {
221
+ // Local storage doesn't support signed URLs
222
+ throw new EmDashStorageError(
223
+ "Local storage does not support signed upload URLs. " +
224
+ "Upload files directly through the API.",
225
+ "NOT_SUPPORTED",
226
+ );
227
+ }
228
+
229
+ getPublicUrl(key: string): string {
230
+ return `${this.baseUrl}/${key}`;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Get content type from file extension
236
+ */
237
+ function getContentType(ext: string): string {
238
+ return mime.getType(ext) ?? "application/octet-stream";
239
+ }
240
+
241
+ /**
242
+ * Create local storage adapter
243
+ * This is the factory function called at runtime
244
+ */
245
+ export function createStorage(config: Record<string, unknown>): Storage {
246
+ const directory = typeof config.directory === "string" ? config.directory : "";
247
+ const baseUrl = typeof config.baseUrl === "string" ? config.baseUrl : "";
248
+ return new LocalStorage({ directory, baseUrl });
249
+ }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * S3-Compatible Storage Implementation
3
+ *
4
+ * Uses the AWS SDK v3 for S3 operations.
5
+ * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.
6
+ */
7
+
8
+ import {
9
+ S3Client,
10
+ PutObjectCommand,
11
+ GetObjectCommand,
12
+ DeleteObjectCommand,
13
+ HeadObjectCommand,
14
+ ListObjectsV2Command,
15
+ type ListObjectsV2Response,
16
+ } from "@aws-sdk/client-s3";
17
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
18
+
19
+ import type {
20
+ Storage,
21
+ S3StorageConfig,
22
+ UploadResult,
23
+ DownloadResult,
24
+ ListResult,
25
+ ListOptions,
26
+ SignedUploadUrl,
27
+ SignedUploadOptions,
28
+ } from "./types.js";
29
+ import { EmDashStorageError } from "./types.js";
30
+
31
+ const TRAILING_SLASH_PATTERN = /\/$/;
32
+
33
+ /** Type guard for AWS SDK errors (have a `name` property) */
34
+ function hasErrorName(error: unknown): error is Error & { name: string } {
35
+ return error instanceof Error && typeof error.name === "string";
36
+ }
37
+
38
+ /**
39
+ * S3-compatible storage implementation
40
+ */
41
+ export class S3Storage implements Storage {
42
+ private client: S3Client;
43
+ private bucket: string;
44
+ private publicUrl?: string;
45
+ private endpoint: string;
46
+
47
+ constructor(config: S3StorageConfig) {
48
+ this.bucket = config.bucket;
49
+ this.publicUrl = config.publicUrl;
50
+ this.endpoint = config.endpoint;
51
+
52
+ this.client = new S3Client({
53
+ endpoint: config.endpoint,
54
+ region: config.region || "auto",
55
+ credentials: {
56
+ accessKeyId: config.accessKeyId,
57
+ secretAccessKey: config.secretAccessKey,
58
+ },
59
+ // Required for R2 and some S3-compatible services
60
+ forcePathStyle: true,
61
+ });
62
+ }
63
+
64
+ async upload(options: {
65
+ key: string;
66
+ body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
67
+ contentType: string;
68
+ }): Promise<UploadResult> {
69
+ try {
70
+ // Convert ReadableStream to Buffer if needed
71
+ let body: Buffer | Uint8Array;
72
+ if (options.body instanceof ReadableStream) {
73
+ const chunks: Uint8Array[] = [];
74
+ const reader = options.body.getReader();
75
+ while (true) {
76
+ const { done, value } = await reader.read();
77
+ if (done) break;
78
+ chunks.push(value);
79
+ }
80
+ body = Buffer.concat(chunks);
81
+ } else {
82
+ body = options.body;
83
+ }
84
+
85
+ await this.client.send(
86
+ new PutObjectCommand({
87
+ Bucket: this.bucket,
88
+ Key: options.key,
89
+ Body: body,
90
+ ContentType: options.contentType,
91
+ }),
92
+ );
93
+
94
+ return {
95
+ key: options.key,
96
+ url: this.getPublicUrl(options.key),
97
+ size: body.length,
98
+ };
99
+ } catch (error) {
100
+ throw new EmDashStorageError(`Failed to upload file: ${options.key}`, "UPLOAD_FAILED", error);
101
+ }
102
+ }
103
+
104
+ async download(key: string): Promise<DownloadResult> {
105
+ try {
106
+ const response = await this.client.send(
107
+ new GetObjectCommand({
108
+ Bucket: this.bucket,
109
+ Key: key,
110
+ }),
111
+ );
112
+
113
+ if (!response.Body) {
114
+ throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND");
115
+ }
116
+
117
+ // Convert SDK stream to web ReadableStream
118
+ const body = response.Body.transformToWebStream();
119
+
120
+ return {
121
+ body,
122
+ contentType: response.ContentType || "application/octet-stream",
123
+ size: response.ContentLength || 0,
124
+ };
125
+ } catch (error) {
126
+ if (
127
+ error instanceof EmDashStorageError ||
128
+ (hasErrorName(error) && error.name === "NoSuchKey")
129
+ ) {
130
+ throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND", error);
131
+ }
132
+ throw new EmDashStorageError(`Failed to download file: ${key}`, "DOWNLOAD_FAILED", error);
133
+ }
134
+ }
135
+
136
+ async delete(key: string): Promise<void> {
137
+ try {
138
+ await this.client.send(
139
+ new DeleteObjectCommand({
140
+ Bucket: this.bucket,
141
+ Key: key,
142
+ }),
143
+ );
144
+ } catch (error) {
145
+ // S3 delete is idempotent, so we ignore "not found" errors
146
+ if (!hasErrorName(error) || error.name !== "NoSuchKey") {
147
+ throw new EmDashStorageError(`Failed to delete file: ${key}`, "DELETE_FAILED", error);
148
+ }
149
+ }
150
+ }
151
+
152
+ async exists(key: string): Promise<boolean> {
153
+ try {
154
+ await this.client.send(
155
+ new HeadObjectCommand({
156
+ Bucket: this.bucket,
157
+ Key: key,
158
+ }),
159
+ );
160
+ return true;
161
+ } catch (error) {
162
+ if (hasErrorName(error) && error.name === "NotFound") {
163
+ return false;
164
+ }
165
+ throw new EmDashStorageError(`Failed to check file existence: ${key}`, "HEAD_FAILED", error);
166
+ }
167
+ }
168
+
169
+ async list(options: ListOptions = {}): Promise<ListResult> {
170
+ try {
171
+ // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- S3 client.send returns generic output; narrowing to ListObjectsV2Response
172
+ const response = (await this.client.send(
173
+ new ListObjectsV2Command({
174
+ Bucket: this.bucket,
175
+ Prefix: options.prefix,
176
+ MaxKeys: options.limit,
177
+ ContinuationToken: options.cursor,
178
+ }),
179
+ )) as ListObjectsV2Response;
180
+
181
+ return {
182
+ files: (response.Contents || []).map(
183
+ (item: { Key?: string; Size?: number; LastModified?: Date; ETag?: string }) => ({
184
+ key: item.Key!,
185
+ size: item.Size || 0,
186
+ lastModified: item.LastModified || new Date(),
187
+ etag: item.ETag,
188
+ }),
189
+ ),
190
+ nextCursor: response.NextContinuationToken,
191
+ };
192
+ } catch (error) {
193
+ throw new EmDashStorageError("Failed to list files", "LIST_FAILED", error);
194
+ }
195
+ }
196
+
197
+ async getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl> {
198
+ try {
199
+ const expiresIn = options.expiresIn || 3600; // 1 hour default
200
+
201
+ const command = new PutObjectCommand({
202
+ Bucket: this.bucket,
203
+ Key: options.key,
204
+ ContentType: options.contentType,
205
+ ContentLength: options.size,
206
+ });
207
+
208
+ const url = await getSignedUrl(this.client, command, { expiresIn });
209
+
210
+ const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
211
+
212
+ return {
213
+ url,
214
+ method: "PUT",
215
+ headers: {
216
+ "Content-Type": options.contentType,
217
+ ...(options.size ? { "Content-Length": String(options.size) } : {}),
218
+ },
219
+ expiresAt,
220
+ };
221
+ } catch (error) {
222
+ throw new EmDashStorageError(
223
+ `Failed to generate signed URL for: ${options.key}`,
224
+ "SIGNED_URL_FAILED",
225
+ error,
226
+ );
227
+ }
228
+ }
229
+
230
+ getPublicUrl(key: string): string {
231
+ if (this.publicUrl) {
232
+ return `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, "")}/${key}`;
233
+ }
234
+ // Default to endpoint + bucket + key
235
+ return `${this.endpoint.replace(TRAILING_SLASH_PATTERN, "")}/${this.bucket}/${key}`;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Create S3 storage adapter
241
+ * This is the factory function called at runtime
242
+ */
243
+ export function createStorage(config: Record<string, unknown>): Storage {
244
+ const { endpoint, bucket, accessKeyId, secretAccessKey, region, publicUrl } = config;
245
+ if (
246
+ typeof endpoint !== "string" ||
247
+ typeof bucket !== "string" ||
248
+ typeof accessKeyId !== "string" ||
249
+ typeof secretAccessKey !== "string"
250
+ ) {
251
+ throw new Error(
252
+ "S3Storage requires 'endpoint', 'bucket', 'accessKeyId', and 'secretAccessKey' string config values",
253
+ );
254
+ }
255
+ return new S3Storage({
256
+ endpoint,
257
+ bucket,
258
+ accessKeyId,
259
+ secretAccessKey,
260
+ region: typeof region === "string" ? region : undefined,
261
+ publicUrl: typeof publicUrl === "string" ? publicUrl : undefined,
262
+ });
263
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Storage Layer Types
3
+ *
4
+ * Defines the interface for S3-compatible storage backends.
5
+ * Works with R2, AWS S3, Minio, and other S3-compatible services.
6
+ */
7
+
8
+ /**
9
+ * Storage configuration for S3-compatible backends
10
+ */
11
+ export interface S3StorageConfig {
12
+ /** S3 endpoint URL (e.g., "https://xxx.r2.cloudflarestorage.com") */
13
+ endpoint: string;
14
+ /** Bucket name */
15
+ bucket: string;
16
+ /** AWS access key ID */
17
+ accessKeyId: string;
18
+ /** AWS secret access key */
19
+ secretAccessKey: string;
20
+ /** Optional region (defaults to "auto" for R2) */
21
+ region?: string;
22
+ /** Optional public URL prefix for generated URLs (e.g., CDN URL) */
23
+ publicUrl?: string;
24
+ }
25
+
26
+ /**
27
+ * Local filesystem storage for development
28
+ */
29
+ export interface LocalStorageConfig {
30
+ /** Directory path for storing files */
31
+ directory: string;
32
+ /** Base URL for serving files */
33
+ baseUrl: string;
34
+ }
35
+
36
+ /**
37
+ * Storage adapter descriptor (serializable config)
38
+ */
39
+ export interface StorageDescriptor {
40
+ /** Module path exporting createStorage function */
41
+ entrypoint: string;
42
+ /** Serializable config passed to createStorage at runtime */
43
+ config: Record<string, unknown>;
44
+ }
45
+
46
+ /**
47
+ * Factory function signature for storage adapters
48
+ *
49
+ * Each adapter accesses its own bindings directly:
50
+ * - R2: imports from cloudflare:workers
51
+ * - S3: uses credentials from config
52
+ * - Local: uses filesystem path from config
53
+ */
54
+ export type CreateStorageFn = (config: Record<string, unknown>) => Storage;
55
+
56
+ /**
57
+ * Upload result
58
+ */
59
+ export interface UploadResult {
60
+ /** Storage key (path within bucket) */
61
+ key: string;
62
+ /** Public URL to access the file */
63
+ url: string;
64
+ /** File size in bytes */
65
+ size: number;
66
+ }
67
+
68
+ /**
69
+ * Download result
70
+ */
71
+ export interface DownloadResult {
72
+ /** File content as readable stream */
73
+ body: ReadableStream<Uint8Array>;
74
+ /** MIME type */
75
+ contentType: string;
76
+ /** File size in bytes */
77
+ size: number;
78
+ }
79
+
80
+ /**
81
+ * Signed URL for direct upload
82
+ */
83
+ export interface SignedUploadUrl {
84
+ /** Signed URL for PUT request */
85
+ url: string;
86
+ /** HTTP method (always PUT) */
87
+ method: "PUT";
88
+ /** Headers to include in the upload request */
89
+ headers: Record<string, string>;
90
+ /** URL expiration time (ISO string) */
91
+ expiresAt: string;
92
+ }
93
+
94
+ /**
95
+ * Options for generating signed upload URL
96
+ */
97
+ export interface SignedUploadOptions {
98
+ /** Storage key (path within bucket) */
99
+ key: string;
100
+ /** MIME type of the file */
101
+ contentType: string;
102
+ /** File size in bytes (for content-length validation) */
103
+ size?: number;
104
+ /** URL expiration in seconds (default: 3600) */
105
+ expiresIn?: number;
106
+ }
107
+
108
+ /**
109
+ * File listing result
110
+ */
111
+ export interface ListResult {
112
+ /** List of files */
113
+ files: FileInfo[];
114
+ /** Cursor for next page (if more results) */
115
+ nextCursor?: string;
116
+ }
117
+
118
+ /**
119
+ * File info from listing
120
+ */
121
+ export interface FileInfo {
122
+ /** Storage key */
123
+ key: string;
124
+ /** File size in bytes */
125
+ size: number;
126
+ /** Last modified date */
127
+ lastModified: Date;
128
+ /** ETag (content hash) */
129
+ etag?: string;
130
+ }
131
+
132
+ /**
133
+ * Options for listing files
134
+ */
135
+ export interface ListOptions {
136
+ /** Filter by key prefix */
137
+ prefix?: string;
138
+ /** Maximum results per page */
139
+ limit?: number;
140
+ /** Cursor from previous list call */
141
+ cursor?: string;
142
+ }
143
+
144
+ /**
145
+ * Storage interface
146
+ *
147
+ * All storage backends must implement this interface.
148
+ */
149
+ export interface Storage {
150
+ /**
151
+ * Upload a file to storage
152
+ */
153
+ upload(options: {
154
+ key: string;
155
+ body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
156
+ contentType: string;
157
+ }): Promise<UploadResult>;
158
+
159
+ /**
160
+ * Download a file from storage
161
+ */
162
+ download(key: string): Promise<DownloadResult>;
163
+
164
+ /**
165
+ * Delete a file from storage
166
+ * Idempotent - does not throw if file doesn't exist
167
+ */
168
+ delete(key: string): Promise<void>;
169
+
170
+ /**
171
+ * Check if a file exists
172
+ */
173
+ exists(key: string): Promise<boolean>;
174
+
175
+ /**
176
+ * List files in storage
177
+ */
178
+ list(options?: ListOptions): Promise<ListResult>;
179
+
180
+ /**
181
+ * Generate a signed URL for direct upload
182
+ * Client uploads directly to storage, bypassing the server
183
+ */
184
+ getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl>;
185
+
186
+ /**
187
+ * Get public URL for a file
188
+ */
189
+ getPublicUrl(key: string): string;
190
+ }
191
+
192
+ /**
193
+ * Storage error with additional context
194
+ */
195
+ export class EmDashStorageError extends Error {
196
+ constructor(
197
+ message: string,
198
+ public code: string,
199
+ public override cause?: unknown,
200
+ ) {
201
+ super(message);
202
+ this.name = "EmDashStorageError";
203
+ }
204
+ }