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,3 @@
1
+ import "../types-DRjfYOEv.mjs";
2
+ import { _ as SeedTaxonomyTerm, a as applySeed, b as ValidationResult, c as SeedCollection, d as SeedFile, f as SeedMenu, g as SeedTaxonomy, h as SeedSection, i as defaultSeed, l as SeedContentEntry, m as SeedRedirect, n as loadSeed, o as SeedApplyOptions, p as SeedMenuItem, r as loadUserSeed, s as SeedApplyResult, t as validateSeed, u as SeedField, v as SeedWidget, y as SeedWidgetArea } from "../validate-CpBtVMsD.mjs";
3
+ export { type SeedApplyOptions, type SeedApplyResult, type SeedCollection, type SeedContentEntry, type SeedField, type SeedFile, type SeedMenu, type SeedMenuItem, type SeedRedirect, type SeedSection, type SeedTaxonomy, type SeedTaxonomyTerm, type SeedWidget, type SeedWidgetArea, type ValidationResult, applySeed, defaultSeed, loadSeed, loadUserSeed, validateSeed };
@@ -0,0 +1,15 @@
1
+ import "../dialect-helpers-B9uSp2GJ.mjs";
2
+ import "../content-D6C2WsZC.mjs";
3
+ import "../base64-MBPo9ozB.mjs";
4
+ import "../types-CMMN0pNg.mjs";
5
+ import "../media-DqHVh136.mjs";
6
+ import { t as applySeed } from "../apply-Bjfq_b4-.mjs";
7
+ import "../registry-D_w5HW4G.mjs";
8
+ import "../redirect-DIfIni3r.mjs";
9
+ import "../byline-CL847F26.mjs";
10
+ import "../loader-fz8Q_3EO.mjs";
11
+ import { t as validateSeed } from "../validate-O7PWmlnq.mjs";
12
+ import { t as defaultSeed } from "../default-Cyi4aAxu.mjs";
13
+ import { n as loadUserSeed, t as loadSeed } from "../load-yOOlckBj.mjs";
14
+
15
+ export { applySeed, defaultSeed, loadSeed, loadUserSeed, validateSeed };
@@ -0,0 +1,70 @@
1
+ import { i as ContentSeo } from "../types-BRuPJGdV.mjs";
2
+
3
+ //#region src/seo/index.d.ts
4
+ /**
5
+ * Content input for SEO functions.
6
+ * Accepts both ContentEntry<T> (from query functions) and ContentItem (internal).
7
+ */
8
+ interface SeoContentInput<T = Record<string, unknown>> {
9
+ /** Content data object */
10
+ data: T & {
11
+ title?: unknown;
12
+ excerpt?: unknown;
13
+ seo?: ContentSeo;
14
+ };
15
+ /** SEO metadata (legacy location, prefer data.seo) */
16
+ seo?: ContentSeo;
17
+ }
18
+ /** Resolved SEO meta tags ready for use in templates */
19
+ interface SeoMeta {
20
+ /** Full <title> tag content (e.g., "Post Title | Site Name") */
21
+ title: string;
22
+ /** Meta description */
23
+ description: string | null;
24
+ /** OG title (same as title by default) */
25
+ ogTitle: string;
26
+ /** OG description */
27
+ ogDescription: string | null;
28
+ /** OG image URL (absolute) */
29
+ ogImage: string | null;
30
+ /** Canonical URL */
31
+ canonical: string | null;
32
+ /** Robots directive (e.g., "noindex, nofollow") or null if default */
33
+ robots: string | null;
34
+ }
35
+ /** Options for generating SEO meta from a content item */
36
+ interface SeoMetaOptions {
37
+ /** Site title for the suffix (e.g., "My Blog") */
38
+ siteTitle?: string;
39
+ /** Site URL origin for building absolute URLs (e.g., "https://example.com") */
40
+ siteUrl?: string;
41
+ /** Title separator between page title and site title */
42
+ titleSeparator?: string;
43
+ /** Path to this content (e.g., "/posts/my-post") for canonical fallback */
44
+ path?: string;
45
+ /** Default OG image URL if content has none */
46
+ defaultOgImage?: string;
47
+ }
48
+ /**
49
+ * Generate resolved SEO meta tags from a content item.
50
+ *
51
+ * Uses the content item's SEO fields, falling back to content data
52
+ * (title from `data.title`, description from `data.excerpt`).
53
+ *
54
+ * @param content - The content item (from getEmDashEntry, etc.)
55
+ * @param options - Configuration for title construction, canonical URLs, etc.
56
+ * @returns Resolved meta tags ready for template use
57
+ */
58
+ declare function getSeoMeta<T>(content: SeoContentInput<T>, options?: SeoMetaOptions): SeoMeta;
59
+ /**
60
+ * Extract SEO data from a content item.
61
+ *
62
+ * Convenience accessor for the raw SEO fields without template resolution.
63
+ *
64
+ * @param content - The content item
65
+ * @returns The content's SEO fields
66
+ */
67
+ declare function getContentSeo<T>(content: SeoContentInput<T>): ContentSeo | undefined;
68
+ //#endregion
69
+ export { SeoContentInput, SeoMeta, SeoMetaOptions, getContentSeo, getSeoMeta };
70
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/seo/index.ts"],"mappings":";;;;;;;UAwCiB,eAAA,KAAoB,MAAA;EA0B9B;EAxBN,IAAA,EAAM,CAAA;IACL,KAAA;IACA,OAAA;IACA,GAAA,GAAM,UAAA;EAAA;EA6BP;EA1BA,GAAA,GAAM,UAAA;AAAA;;UAIU,OAAA;EA4BF;EA1Bd,KAAA;EAuCyB;EArCzB,WAAA;EAqCsD;EAnCtD,OAAA;EAmCmE;EAjCnE,aAAA;EAiCgG;EA/BhG,OAAA;EA+B0B;EA7B1B,SAAA;EA6BsD;EA3BtD,MAAA;AAAA;;UAIgB,cAAA;EAuBgF;EArBhG,SAAA;EAsFe;EApFf,OAAA;EAoF4B;EAlF5B,cAAA;EAkFyC;EAhFzC,IAAA;EAgFwE;EA9ExE,cAAA;AAAA;;;;;;;;;;;iBAae,UAAA,GAAA,CAAc,OAAA,EAAS,eAAA,CAAgB,CAAA,GAAI,OAAA,GAAS,cAAA,GAAsB,OAAA;;;;;;;;;iBAiE1E,aAAA,GAAA,CAAiB,OAAA,EAAS,eAAA,CAAgB,CAAA,IAAK,UAAA"}
@@ -0,0 +1,70 @@
1
+ //#region src/seo/index.ts
2
+ const TRAILING_SLASH_RE = /\/$/;
3
+ const ABSOLUTE_URL_RE = /^https?:\/\//i;
4
+ /**
5
+ * Generate resolved SEO meta tags from a content item.
6
+ *
7
+ * Uses the content item's SEO fields, falling back to content data
8
+ * (title from `data.title`, description from `data.excerpt`).
9
+ *
10
+ * @param content - The content item (from getEmDashEntry, etc.)
11
+ * @param options - Configuration for title construction, canonical URLs, etc.
12
+ * @returns Resolved meta tags ready for template use
13
+ */
14
+ function getSeoMeta(content, options = {}) {
15
+ const { siteTitle, siteUrl, path, defaultOgImage } = options;
16
+ const separator = options.titleSeparator || " | ";
17
+ const seo = content.seo ?? content.data.seo ?? {
18
+ title: null,
19
+ description: null,
20
+ image: null,
21
+ canonical: null,
22
+ noIndex: false
23
+ };
24
+ const pageTitle = seo.title || (typeof content.data.title === "string" ? content.data.title : null) || "";
25
+ const fullTitle = siteTitle && pageTitle ? `${pageTitle}${separator}${siteTitle}` : pageTitle;
26
+ const description = seo.description || (typeof content.data.excerpt === "string" ? content.data.excerpt : null) || null;
27
+ const ogImage = seo.image ? buildMediaUrl(seo.image, siteUrl) : defaultOgImage ?? null;
28
+ let canonical = null;
29
+ if (seo.canonical) if (siteUrl && !seo.canonical.startsWith("/") && !ABSOLUTE_URL_RE.test(seo.canonical)) canonical = `${siteUrl.replace(TRAILING_SLASH_RE, "")}/${seo.canonical}`;
30
+ else canonical = seo.canonical;
31
+ else if (siteUrl && path) {
32
+ const safePath = path.startsWith("/") ? path : `/${path}`;
33
+ canonical = `${siteUrl.replace(TRAILING_SLASH_RE, "")}${safePath}`;
34
+ }
35
+ const robots = seo.noIndex ? "noindex, nofollow" : null;
36
+ return {
37
+ title: fullTitle,
38
+ description,
39
+ ogTitle: pageTitle || fullTitle,
40
+ ogDescription: description,
41
+ ogImage,
42
+ canonical,
43
+ robots
44
+ };
45
+ }
46
+ /**
47
+ * Extract SEO data from a content item.
48
+ *
49
+ * Convenience accessor for the raw SEO fields without template resolution.
50
+ *
51
+ * @param content - The content item
52
+ * @returns The content's SEO fields
53
+ */
54
+ function getContentSeo(content) {
55
+ return content.seo ?? content.data.seo;
56
+ }
57
+ /**
58
+ * Build a media URL from a media reference ID.
59
+ * If it's already an absolute URL, return as-is.
60
+ */
61
+ function buildMediaUrl(imageRef, siteUrl) {
62
+ if (ABSOLUTE_URL_RE.test(imageRef)) return imageRef;
63
+ const mediaPath = `/_emdash/api/media/file/${imageRef}`;
64
+ if (siteUrl) return `${siteUrl.replace(TRAILING_SLASH_RE, "")}${mediaPath}`;
65
+ return mediaPath;
66
+ }
67
+
68
+ //#endregion
69
+ export { getContentSeo, getSeoMeta };
70
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/seo/index.ts"],"sourcesContent":["/**\n * SEO Helpers\n *\n * Public API functions for generating SEO meta tags in Astro templates.\n *\n * @example\n * ```astro\n * ---\n * import { getEmDashEntry } from \"emdash\";\n * import { getSeoMeta } from \"emdash/seo\";\n *\n * const post = await getEmDashEntry(\"posts\", Astro.params.slug);\n * const meta = await getSeoMeta(post, {\n * siteTitle: \"My Blog\",\n * siteUrl: Astro.url.origin,\n * });\n * ---\n * <html>\n * <head>\n * <title>{meta.title}</title>\n * <meta name=\"description\" content={meta.description} />\n * <meta property=\"og:title\" content={meta.ogTitle} />\n * <meta property=\"og:description\" content={meta.ogDescription} />\n * {meta.ogImage && <meta property=\"og:image\" content={meta.ogImage} />}\n * <link rel=\"canonical\" href={meta.canonical} />\n * {meta.robots && <meta name=\"robots\" content={meta.robots} />}\n * </head>\n * </html>\n * ```\n */\n\nimport type { ContentSeo } from \"../database/repositories/types.js\";\n\nconst TRAILING_SLASH_RE = /\\/$/;\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//i;\n\n/**\n * Content input for SEO functions.\n * Accepts both ContentEntry<T> (from query functions) and ContentItem (internal).\n */\nexport interface SeoContentInput<T = Record<string, unknown>> {\n\t/** Content data object */\n\tdata: T & {\n\t\ttitle?: unknown;\n\t\texcerpt?: unknown;\n\t\tseo?: ContentSeo;\n\t};\n\t/** SEO metadata (legacy location, prefer data.seo) */\n\tseo?: ContentSeo;\n}\n\n/** Resolved SEO meta tags ready for use in templates */\nexport interface SeoMeta {\n\t/** Full <title> tag content (e.g., \"Post Title | Site Name\") */\n\ttitle: string;\n\t/** Meta description */\n\tdescription: string | null;\n\t/** OG title (same as title by default) */\n\togTitle: string;\n\t/** OG description */\n\togDescription: string | null;\n\t/** OG image URL (absolute) */\n\togImage: string | null;\n\t/** Canonical URL */\n\tcanonical: string | null;\n\t/** Robots directive (e.g., \"noindex, nofollow\") or null if default */\n\trobots: string | null;\n}\n\n/** Options for generating SEO meta from a content item */\nexport interface SeoMetaOptions {\n\t/** Site title for the suffix (e.g., \"My Blog\") */\n\tsiteTitle?: string;\n\t/** Site URL origin for building absolute URLs (e.g., \"https://example.com\") */\n\tsiteUrl?: string;\n\t/** Title separator between page title and site title */\n\ttitleSeparator?: string;\n\t/** Path to this content (e.g., \"/posts/my-post\") for canonical fallback */\n\tpath?: string;\n\t/** Default OG image URL if content has none */\n\tdefaultOgImage?: string;\n}\n\n/**\n * Generate resolved SEO meta tags from a content item.\n *\n * Uses the content item's SEO fields, falling back to content data\n * (title from `data.title`, description from `data.excerpt`).\n *\n * @param content - The content item (from getEmDashEntry, etc.)\n * @param options - Configuration for title construction, canonical URLs, etc.\n * @returns Resolved meta tags ready for template use\n */\nexport function getSeoMeta<T>(content: SeoContentInput<T>, options: SeoMetaOptions = {}): SeoMeta {\n\tconst { siteTitle, siteUrl, path, defaultOgImage } = options;\n\tconst separator = options.titleSeparator || \" | \";\n\t// SEO can be in content.seo (ContentItem) or content.data.seo (ContentEntry)\n\tconst seo = content.seo ??\n\t\tcontent.data.seo ?? {\n\t\t\ttitle: null,\n\t\t\tdescription: null,\n\t\t\timage: null,\n\t\t\tcanonical: null,\n\t\t\tnoIndex: false,\n\t\t};\n\n\t// Title: SEO title > content title > fallback\n\tconst pageTitle =\n\t\tseo.title || (typeof content.data.title === \"string\" ? content.data.title : null) || \"\";\n\n\tconst fullTitle = siteTitle && pageTitle ? `${pageTitle}${separator}${siteTitle}` : pageTitle;\n\n\t// Description: SEO description > excerpt\n\tconst description =\n\t\tseo.description ||\n\t\t(typeof content.data.excerpt === \"string\" ? content.data.excerpt : null) ||\n\t\tnull;\n\n\t// OG image: SEO image > default\n\tconst ogImage = seo.image ? buildMediaUrl(seo.image, siteUrl) : (defaultOgImage ?? null);\n\n\t// Canonical: explicit > path-based > null\n\tlet canonical: string | null = null;\n\tif (seo.canonical) {\n\t\t// Ensure relative canonical paths get a leading slash so we don't\n\t\t// produce \"https://example.composts/x\" when joined with siteUrl\n\t\tif (siteUrl && !seo.canonical.startsWith(\"/\") && !ABSOLUTE_URL_RE.test(seo.canonical)) {\n\t\t\tcanonical = `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}/${seo.canonical}`;\n\t\t} else {\n\t\t\tcanonical = seo.canonical;\n\t\t}\n\t} else if (siteUrl && path) {\n\t\tconst safePath = path.startsWith(\"/\") ? path : `/${path}`;\n\t\tcanonical = `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${safePath}`;\n\t}\n\n\t// Robots\n\tconst robots = seo.noIndex ? \"noindex, nofollow\" : null;\n\n\treturn {\n\t\ttitle: fullTitle,\n\t\tdescription,\n\t\togTitle: pageTitle || fullTitle,\n\t\togDescription: description,\n\t\togImage,\n\t\tcanonical,\n\t\trobots,\n\t};\n}\n\n/**\n * Extract SEO data from a content item.\n *\n * Convenience accessor for the raw SEO fields without template resolution.\n *\n * @param content - The content item\n * @returns The content's SEO fields\n */\nexport function getContentSeo<T>(content: SeoContentInput<T>): ContentSeo | undefined {\n\treturn content.seo ?? content.data.seo;\n}\n\n/**\n * Build a media URL from a media reference ID.\n * If it's already an absolute URL, return as-is.\n */\nfunction buildMediaUrl(imageRef: string, siteUrl?: string): string {\n\t// If already an absolute URL, return as-is\n\tif (ABSOLUTE_URL_RE.test(imageRef)) {\n\t\treturn imageRef;\n\t}\n\n\t// Build from media API path\n\tconst mediaPath = `/_emdash/api/media/file/${imageRef}`;\n\tif (siteUrl) {\n\t\treturn `${siteUrl.replace(TRAILING_SLASH_RE, \"\")}${mediaPath}`;\n\t}\n\treturn mediaPath;\n}\n"],"mappings":";AAiCA,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;;;;;;;;;;;AA2DxB,SAAgB,WAAc,SAA6B,UAA0B,EAAE,EAAW;CACjG,MAAM,EAAE,WAAW,SAAS,MAAM,mBAAmB;CACrD,MAAM,YAAY,QAAQ,kBAAkB;CAE5C,MAAM,MAAM,QAAQ,OACnB,QAAQ,KAAK,OAAO;EACnB,OAAO;EACP,aAAa;EACb,OAAO;EACP,WAAW;EACX,SAAS;EACT;CAGF,MAAM,YACL,IAAI,UAAU,OAAO,QAAQ,KAAK,UAAU,WAAW,QAAQ,KAAK,QAAQ,SAAS;CAEtF,MAAM,YAAY,aAAa,YAAY,GAAG,YAAY,YAAY,cAAc;CAGpF,MAAM,cACL,IAAI,gBACH,OAAO,QAAQ,KAAK,YAAY,WAAW,QAAQ,KAAK,UAAU,SACnE;CAGD,MAAM,UAAU,IAAI,QAAQ,cAAc,IAAI,OAAO,QAAQ,GAAI,kBAAkB;CAGnF,IAAI,YAA2B;AAC/B,KAAI,IAAI,UAGP,KAAI,WAAW,CAAC,IAAI,UAAU,WAAW,IAAI,IAAI,CAAC,gBAAgB,KAAK,IAAI,UAAU,CACpF,aAAY,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,CAAC,GAAG,IAAI;KAE7D,aAAY,IAAI;UAEP,WAAW,MAAM;EAC3B,MAAM,WAAW,KAAK,WAAW,IAAI,GAAG,OAAO,IAAI;AACnD,cAAY,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG;;CAIzD,MAAM,SAAS,IAAI,UAAU,sBAAsB;AAEnD,QAAO;EACN,OAAO;EACP;EACA,SAAS,aAAa;EACtB,eAAe;EACf;EACA;EACA;EACA;;;;;;;;;;AAWF,SAAgB,cAAiB,SAAqD;AACrF,QAAO,QAAQ,OAAO,QAAQ,KAAK;;;;;;AAOpC,SAAS,cAAc,UAAkB,SAA0B;AAElE,KAAI,gBAAgB,KAAK,SAAS,CACjC,QAAO;CAIR,MAAM,YAAY,2BAA2B;AAC7C,KAAI,QACH,QAAO,GAAG,QAAQ,QAAQ,mBAAmB,GAAG,GAAG;AAEpD,QAAO"}
@@ -0,0 +1,39 @@
1
+ import { a as ListOptions, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-DaNLHo_T.mjs";
2
+
3
+ //#region src/storage/local.d.ts
4
+ /**
5
+ * Local filesystem storage implementation
6
+ */
7
+ declare class LocalStorage implements Storage {
8
+ /** Resolved absolute base directory for all stored files */
9
+ private directory;
10
+ private baseUrl;
11
+ constructor(config: LocalStorageConfig);
12
+ /**
13
+ * Resolve a storage key to an absolute file path, ensuring it stays
14
+ * within the configured storage directory. Uses path.resolve() for
15
+ * canonical resolution rather than regex stripping.
16
+ *
17
+ * @throws EmDashStorageError if the resolved path escapes the base directory
18
+ */
19
+ private getFilePath;
20
+ upload(options: {
21
+ key: string;
22
+ body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
23
+ contentType: string;
24
+ }): Promise<UploadResult>;
25
+ download(key: string): Promise<DownloadResult>;
26
+ delete(key: string): Promise<void>;
27
+ exists(key: string): Promise<boolean>;
28
+ list(options?: ListOptions): Promise<ListResult>;
29
+ getSignedUploadUrl(_options: SignedUploadOptions): Promise<SignedUploadUrl>;
30
+ getPublicUrl(key: string): string;
31
+ }
32
+ /**
33
+ * Create local storage adapter
34
+ * This is the factory function called at runtime
35
+ */
36
+ declare function createStorage(config: Record<string, unknown>): Storage;
37
+ //#endregion
38
+ export { LocalStorage, createStorage };
39
+ //# sourceMappingURL=local.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.mts","names":[],"sources":["../../src/storage/local.ts"],"mappings":";;;;;;cAuCa,YAAA,YAAwB,OAAA;EA+BP;EAAA,QA7BrB,SAAA;EAAA,QACA,OAAA;cAEI,MAAA,EAAQ,kBAAA;EAqES;;;;;;;EAAA,QAzDrB,WAAA;EAYF,MAAA,CAAO,OAAA;IACZ,GAAA;IACA,IAAA,EAAM,MAAA,GAAS,UAAA,GAAa,cAAA,CAAe,UAAA;IAC3C,WAAA;EAAA,IACG,OAAA,CAAQ,YAAA;EAyCN,QAAA,CAAS,GAAA,WAAc,OAAA,CAAQ,cAAA;EAkC/B,MAAA,CAAO,GAAA,WAAc,OAAA;EAYrB,MAAA,CAAO,GAAA,WAAc,OAAA;EAUrB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA,CAAQ,UAAA;EAsDzC,kBAAA,CAAmB,QAAA,EAAU,mBAAA,GAAsB,OAAA,CAAQ,eAAA;EASjE,YAAA,CAAa,GAAA;AAAA;;;;;iBAgBE,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,OAAA"}
@@ -0,0 +1,166 @@
1
+ import { t as EmDashStorageError } from "../types-CUBbjgmP.mjs";
2
+ import mime from "mime/lite";
3
+ import * as path from "node:path";
4
+ import { createReadStream, existsSync } from "node:fs";
5
+ import * as fs from "node:fs/promises";
6
+ import { Readable } from "node:stream";
7
+
8
+ //#region src/storage/local.ts
9
+ /**
10
+ * Local Filesystem Storage Implementation
11
+ *
12
+ * For development and testing. Stores files in a local directory.
13
+ */
14
+ /** Type guard for Node.js ErrnoException */
15
+ function isNodeError(error) {
16
+ return error instanceof Error && "code" in error;
17
+ }
18
+ /** Pattern to remove leading slashes */
19
+ const LEADING_SLASH_PATTERN = /^\//;
20
+ /** Pattern to remove trailing slashes */
21
+ const TRAILING_SLASH_PATTERN = /\/$/;
22
+ /**
23
+ * Local filesystem storage implementation
24
+ */
25
+ var LocalStorage = class {
26
+ /** Resolved absolute base directory for all stored files */
27
+ directory;
28
+ baseUrl;
29
+ constructor(config) {
30
+ this.directory = path.resolve(config.directory);
31
+ this.baseUrl = config.baseUrl.replace(TRAILING_SLASH_PATTERN, "");
32
+ }
33
+ /**
34
+ * Resolve a storage key to an absolute file path, ensuring it stays
35
+ * within the configured storage directory. Uses path.resolve() for
36
+ * canonical resolution rather than regex stripping.
37
+ *
38
+ * @throws EmDashStorageError if the resolved path escapes the base directory
39
+ */
40
+ getFilePath(key) {
41
+ const normalizedKey = key.replace(LEADING_SLASH_PATTERN, "");
42
+ const resolved = path.resolve(this.directory, normalizedKey);
43
+ if (!resolved.startsWith(this.directory + path.sep) && resolved !== this.directory) throw new EmDashStorageError("Invalid file path", "INVALID_PATH");
44
+ return resolved;
45
+ }
46
+ async upload(options) {
47
+ try {
48
+ const filePath = this.getFilePath(options.key);
49
+ const dir = path.dirname(filePath);
50
+ await fs.mkdir(dir, { recursive: true });
51
+ let buffer;
52
+ if (options.body instanceof ReadableStream) {
53
+ const chunks = [];
54
+ const reader = options.body.getReader();
55
+ while (true) {
56
+ const { done, value } = await reader.read();
57
+ if (done) break;
58
+ chunks.push(value);
59
+ }
60
+ buffer = Buffer.concat(chunks);
61
+ } else if (options.body instanceof Uint8Array) buffer = Buffer.from(options.body);
62
+ else buffer = options.body;
63
+ await fs.writeFile(filePath, buffer);
64
+ return {
65
+ key: options.key,
66
+ url: this.getPublicUrl(options.key),
67
+ size: buffer.length
68
+ };
69
+ } catch (error) {
70
+ throw new EmDashStorageError(`Failed to upload file: ${options.key}`, "UPLOAD_FAILED", error);
71
+ }
72
+ }
73
+ async download(key) {
74
+ try {
75
+ const filePath = this.getFilePath(key);
76
+ if (!existsSync(filePath)) throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND");
77
+ const stat = await fs.stat(filePath);
78
+ const nodeStream = createReadStream(filePath);
79
+ return {
80
+ body: Readable.toWeb(nodeStream),
81
+ contentType: getContentType(path.extname(key).toLowerCase()),
82
+ size: stat.size
83
+ };
84
+ } catch (error) {
85
+ if (error instanceof EmDashStorageError) throw error;
86
+ throw new EmDashStorageError(`Failed to download file: ${key}`, "DOWNLOAD_FAILED", error);
87
+ }
88
+ }
89
+ async delete(key) {
90
+ try {
91
+ const filePath = this.getFilePath(key);
92
+ await fs.unlink(filePath);
93
+ } catch (error) {
94
+ if (!isNodeError(error) || error.code !== "ENOENT") throw new EmDashStorageError(`Failed to delete file: ${key}`, "DELETE_FAILED", error);
95
+ }
96
+ }
97
+ async exists(key) {
98
+ try {
99
+ const filePath = this.getFilePath(key);
100
+ await fs.access(filePath);
101
+ return true;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+ async list(options = {}) {
107
+ try {
108
+ const prefix = options.prefix || "";
109
+ const searchDir = path.resolve(this.directory, path.dirname(prefix));
110
+ if (!searchDir.startsWith(this.directory + path.sep) && searchDir !== this.directory) throw new EmDashStorageError("Invalid list prefix", "INVALID_PATH");
111
+ const prefixBase = path.basename(prefix);
112
+ try {
113
+ await fs.access(searchDir);
114
+ } catch {
115
+ return { files: [] };
116
+ }
117
+ const entries = await fs.readdir(searchDir, { withFileTypes: true });
118
+ const files = [];
119
+ for (const entry of entries) if (entry.isFile() && entry.name.startsWith(prefixBase)) {
120
+ const key = path.join(path.dirname(prefix), entry.name);
121
+ const filePath = path.join(searchDir, entry.name);
122
+ const stat = await fs.stat(filePath);
123
+ files.push({
124
+ key,
125
+ size: stat.size,
126
+ lastModified: stat.mtime
127
+ });
128
+ }
129
+ files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
130
+ const startIndex = options.cursor ? parseInt(options.cursor, 10) : 0;
131
+ const limit = options.limit || 1e3;
132
+ return {
133
+ files: files.slice(startIndex, startIndex + limit),
134
+ nextCursor: startIndex + limit < files.length ? String(startIndex + limit) : void 0
135
+ };
136
+ } catch (error) {
137
+ throw new EmDashStorageError("Failed to list files", "LIST_FAILED", error);
138
+ }
139
+ }
140
+ async getSignedUploadUrl(_options) {
141
+ throw new EmDashStorageError("Local storage does not support signed upload URLs. Upload files directly through the API.", "NOT_SUPPORTED");
142
+ }
143
+ getPublicUrl(key) {
144
+ return `${this.baseUrl}/${key}`;
145
+ }
146
+ };
147
+ /**
148
+ * Get content type from file extension
149
+ */
150
+ function getContentType(ext) {
151
+ return mime.getType(ext) ?? "application/octet-stream";
152
+ }
153
+ /**
154
+ * Create local storage adapter
155
+ * This is the factory function called at runtime
156
+ */
157
+ function createStorage(config) {
158
+ return new LocalStorage({
159
+ directory: typeof config.directory === "string" ? config.directory : "",
160
+ baseUrl: typeof config.baseUrl === "string" ? config.baseUrl : ""
161
+ });
162
+ }
163
+
164
+ //#endregion
165
+ export { LocalStorage, createStorage };
166
+ //# sourceMappingURL=local.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.mjs","names":[],"sources":["../../src/storage/local.ts"],"sourcesContent":["/**\n * Local Filesystem Storage Implementation\n *\n * For development and testing. Stores files in a local directory.\n */\n\nimport { createReadStream, existsSync } from \"node:fs\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { Readable } from \"node:stream\";\n\nimport mime from \"mime/lite\";\n\n/** Type guard for Node.js ErrnoException */\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n\treturn error instanceof Error && \"code\" in error;\n}\n\nimport type {\n\tStorage,\n\tLocalStorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\n/** Pattern to remove leading slashes */\nconst LEADING_SLASH_PATTERN = /^\\//;\n\n/** Pattern to remove trailing slashes */\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/**\n * Local filesystem storage implementation\n */\nexport class LocalStorage implements Storage {\n\t/** Resolved absolute base directory for all stored files */\n\tprivate directory: string;\n\tprivate baseUrl: string;\n\n\tconstructor(config: LocalStorageConfig) {\n\t\tthis.directory = path.resolve(config.directory);\n\t\tthis.baseUrl = config.baseUrl.replace(TRAILING_SLASH_PATTERN, \"\");\n\t}\n\n\t/**\n\t * Resolve a storage key to an absolute file path, ensuring it stays\n\t * within the configured storage directory. Uses path.resolve() for\n\t * canonical resolution rather than regex stripping.\n\t *\n\t * @throws EmDashStorageError if the resolved path escapes the base directory\n\t */\n\tprivate getFilePath(key: string): string {\n\t\tconst normalizedKey = key.replace(LEADING_SLASH_PATTERN, \"\");\n\t\tconst resolved = path.resolve(this.directory, normalizedKey);\n\n\t\t// Verify the resolved path is within the base directory\n\t\tif (!resolved.startsWith(this.directory + path.sep) && resolved !== this.directory) {\n\t\t\tthrow new EmDashStorageError(\"Invalid file path\", \"INVALID_PATH\");\n\t\t}\n\n\t\treturn resolved;\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(options.key);\n\t\t\tconst dir = path.dirname(filePath);\n\n\t\t\t// Ensure directory exists\n\t\t\tawait fs.mkdir(dir, { recursive: true });\n\n\t\t\t// Convert body to buffer\n\t\t\tlet buffer: Buffer;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbuffer = Buffer.concat(chunks);\n\t\t\t} else if (options.body instanceof Uint8Array) {\n\t\t\t\tbuffer = Buffer.from(options.body);\n\t\t\t} else {\n\t\t\t\tbuffer = options.body;\n\t\t\t}\n\n\t\t\tawait fs.writeFile(filePath, buffer);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: buffer.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to upload file: ${options.key}`,\n\t\t\t\t\"UPLOAD_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\n\t\t\tif (!existsSync(filePath)) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\tconst stat = await fs.stat(filePath);\n\t\t\tconst nodeStream = createReadStream(filePath);\n\n\t\t\t// Convert Node.js stream to web ReadableStream\n\t\t\t// Readable.toWeb returns ReadableStream (which is ReadableStream<unknown>),\n\t\t\t// but Node ReadStreams produce Buffer/Uint8Array chunks\n\t\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Readable.toWeb returns ReadableStream<unknown>; Node ReadStreams produce Uint8Array chunks\n\t\t\tconst webStream: ReadableStream<Uint8Array> = Readable.toWeb(\n\t\t\t\tnodeStream,\n\t\t\t) as ReadableStream<Uint8Array>;\n\n\t\t\t// Infer content type from extension\n\t\t\tconst ext = path.extname(key).toLowerCase();\n\t\t\tconst contentType = getContentType(ext);\n\n\t\t\treturn {\n\t\t\t\tbody: webStream,\n\t\t\t\tcontentType,\n\t\t\t\tsize: stat.size,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (error instanceof EmDashStorageError) throw error;\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\t\t\tawait fs.unlink(filePath);\n\t\t} catch (error) {\n\t\t\t// Ignore \"file not found\" errors (idempotent delete)\n\t\t\tif (!isNodeError(error) || error.code !== \"ENOENT\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst filePath = this.getFilePath(key);\n\t\t\tawait fs.access(filePath);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\tconst prefix = options.prefix || \"\";\n\t\t\tconst searchDir = path.resolve(this.directory, path.dirname(prefix));\n\n\t\t\t// Validate the search directory stays within the base directory\n\t\t\tif (!searchDir.startsWith(this.directory + path.sep) && searchDir !== this.directory) {\n\t\t\t\tthrow new EmDashStorageError(\"Invalid list prefix\", \"INVALID_PATH\");\n\t\t\t}\n\n\t\t\tconst prefixBase = path.basename(prefix);\n\n\t\t\t// Ensure directory exists\n\t\t\ttry {\n\t\t\t\tawait fs.access(searchDir);\n\t\t\t} catch {\n\t\t\t\treturn { files: [] };\n\t\t\t}\n\n\t\t\tconst entries = await fs.readdir(searchDir, { withFileTypes: true });\n\t\t\tconst files: ListResult[\"files\"] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.isFile() && entry.name.startsWith(prefixBase)) {\n\t\t\t\t\tconst key = path.join(path.dirname(prefix), entry.name);\n\t\t\t\t\tconst filePath = path.join(searchDir, entry.name);\n\t\t\t\t\tconst stat = await fs.stat(filePath);\n\n\t\t\t\t\tfiles.push({\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tsize: stat.size,\n\t\t\t\t\t\tlastModified: stat.mtime,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sort by last modified (newest first)\n\t\t\tfiles.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());\n\n\t\t\t// Apply limit and cursor (simple implementation)\n\t\t\tconst startIndex = options.cursor ? parseInt(options.cursor, 10) : 0;\n\t\t\tconst limit = options.limit || 1000;\n\t\t\tconst paginatedFiles = files.slice(startIndex, startIndex + limit);\n\t\t\tconst hasMore = startIndex + limit < files.length;\n\n\t\t\treturn {\n\t\t\t\tfiles: paginatedFiles,\n\t\t\t\tnextCursor: hasMore ? String(startIndex + limit) : undefined,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(_options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\t// Local storage doesn't support signed URLs\n\t\tthrow new EmDashStorageError(\n\t\t\t\"Local storage does not support signed upload URLs. \" +\n\t\t\t\t\"Upload files directly through the API.\",\n\t\t\t\"NOT_SUPPORTED\",\n\t\t);\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\treturn `${this.baseUrl}/${key}`;\n\t}\n}\n\n/**\n * Get content type from file extension\n */\nfunction getContentType(ext: string): string {\n\treturn mime.getType(ext) ?? \"application/octet-stream\";\n}\n\n/**\n * Create local storage adapter\n * This is the factory function called at runtime\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\tconst directory = typeof config.directory === \"string\" ? config.directory : \"\";\n\tconst baseUrl = typeof config.baseUrl === \"string\" ? config.baseUrl : \"\";\n\treturn new LocalStorage({ directory, baseUrl });\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, u as SignedUploadUrl } from "../types-DaNLHo_T.mjs";
2
+
3
+ //#region src/storage/s3.d.ts
4
+ /**
5
+ * S3-compatible storage implementation
6
+ */
7
+ declare class S3Storage implements Storage {
8
+ private client;
9
+ private bucket;
10
+ private publicUrl?;
11
+ private endpoint;
12
+ constructor(config: S3StorageConfig);
13
+ upload(options: {
14
+ key: string;
15
+ body: Buffer | Uint8Array | ReadableStream<Uint8Array>;
16
+ contentType: string;
17
+ }): Promise<UploadResult>;
18
+ download(key: string): Promise<DownloadResult>;
19
+ delete(key: string): Promise<void>;
20
+ exists(key: string): Promise<boolean>;
21
+ list(options?: ListOptions): Promise<ListResult>;
22
+ getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl>;
23
+ getPublicUrl(key: string): string;
24
+ }
25
+ /**
26
+ * Create S3 storage adapter
27
+ * This is the factory function called at runtime
28
+ */
29
+ declare function createStorage(config: Record<string, unknown>): Storage;
30
+ //#endregion
31
+ export { S3Storage, createStorage };
32
+ //# sourceMappingURL=s3.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.mts","names":[],"sources":["../../src/storage/s3.ts"],"mappings":";;;;;;cAwCa,SAAA,YAAqB,OAAA;EAAA,QACzB,MAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,QAAA;cAEI,MAAA,EAAQ,eAAA;EAiBd,MAAA,CAAO,OAAA;IACZ,GAAA;IACA,IAAA,EAAM,MAAA,GAAS,UAAA,GAAa,cAAA,CAAe,UAAA;IAC3C,WAAA;EAAA,IACG,OAAA,CAAQ,YAAA;EAwCN,QAAA,CAAS,GAAA,WAAc,OAAA,CAAQ,cAAA;EAgC/B,MAAA,CAAO,GAAA,WAAc,OAAA;EAgBrB,MAAA,CAAO,GAAA,WAAc,OAAA;EAqBrB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA,CAAQ,UAAA;EA4BzC,kBAAA,CAAmB,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,eAAA;EAiChE,YAAA,CAAa,GAAA;AAAA;;;;;iBAaE,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,OAAA"}
@@ -0,0 +1,175 @@
1
+ import { t as EmDashStorageError } from "../types-CUBbjgmP.mjs";
2
+ import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
3
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
4
+
5
+ //#region src/storage/s3.ts
6
+ /**
7
+ * S3-Compatible Storage Implementation
8
+ *
9
+ * Uses the AWS SDK v3 for S3 operations.
10
+ * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.
11
+ */
12
+ const TRAILING_SLASH_PATTERN = /\/$/;
13
+ /** Type guard for AWS SDK errors (have a `name` property) */
14
+ function hasErrorName(error) {
15
+ return error instanceof Error && typeof error.name === "string";
16
+ }
17
+ /**
18
+ * S3-compatible storage implementation
19
+ */
20
+ var S3Storage = class {
21
+ client;
22
+ bucket;
23
+ publicUrl;
24
+ endpoint;
25
+ constructor(config) {
26
+ this.bucket = config.bucket;
27
+ this.publicUrl = config.publicUrl;
28
+ this.endpoint = config.endpoint;
29
+ this.client = new S3Client({
30
+ endpoint: config.endpoint,
31
+ region: config.region || "auto",
32
+ credentials: {
33
+ accessKeyId: config.accessKeyId,
34
+ secretAccessKey: config.secretAccessKey
35
+ },
36
+ forcePathStyle: true
37
+ });
38
+ }
39
+ async upload(options) {
40
+ try {
41
+ let body;
42
+ if (options.body instanceof ReadableStream) {
43
+ const chunks = [];
44
+ const reader = options.body.getReader();
45
+ while (true) {
46
+ const { done, value } = await reader.read();
47
+ if (done) break;
48
+ chunks.push(value);
49
+ }
50
+ body = Buffer.concat(chunks);
51
+ } else body = options.body;
52
+ await this.client.send(new PutObjectCommand({
53
+ Bucket: this.bucket,
54
+ Key: options.key,
55
+ Body: body,
56
+ ContentType: options.contentType
57
+ }));
58
+ return {
59
+ key: options.key,
60
+ url: this.getPublicUrl(options.key),
61
+ size: body.length
62
+ };
63
+ } catch (error) {
64
+ throw new EmDashStorageError(`Failed to upload file: ${options.key}`, "UPLOAD_FAILED", error);
65
+ }
66
+ }
67
+ async download(key) {
68
+ try {
69
+ const response = await this.client.send(new GetObjectCommand({
70
+ Bucket: this.bucket,
71
+ Key: key
72
+ }));
73
+ if (!response.Body) throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND");
74
+ return {
75
+ body: response.Body.transformToWebStream(),
76
+ contentType: response.ContentType || "application/octet-stream",
77
+ size: response.ContentLength || 0
78
+ };
79
+ } catch (error) {
80
+ if (error instanceof EmDashStorageError || hasErrorName(error) && error.name === "NoSuchKey") throw new EmDashStorageError(`File not found: ${key}`, "NOT_FOUND", error);
81
+ throw new EmDashStorageError(`Failed to download file: ${key}`, "DOWNLOAD_FAILED", error);
82
+ }
83
+ }
84
+ async delete(key) {
85
+ try {
86
+ await this.client.send(new DeleteObjectCommand({
87
+ Bucket: this.bucket,
88
+ Key: key
89
+ }));
90
+ } catch (error) {
91
+ if (!hasErrorName(error) || error.name !== "NoSuchKey") throw new EmDashStorageError(`Failed to delete file: ${key}`, "DELETE_FAILED", error);
92
+ }
93
+ }
94
+ async exists(key) {
95
+ try {
96
+ await this.client.send(new HeadObjectCommand({
97
+ Bucket: this.bucket,
98
+ Key: key
99
+ }));
100
+ return true;
101
+ } catch (error) {
102
+ if (hasErrorName(error) && error.name === "NotFound") return false;
103
+ throw new EmDashStorageError(`Failed to check file existence: ${key}`, "HEAD_FAILED", error);
104
+ }
105
+ }
106
+ async list(options = {}) {
107
+ try {
108
+ const response = await this.client.send(new ListObjectsV2Command({
109
+ Bucket: this.bucket,
110
+ Prefix: options.prefix,
111
+ MaxKeys: options.limit,
112
+ ContinuationToken: options.cursor
113
+ }));
114
+ return {
115
+ files: (response.Contents || []).map((item) => ({
116
+ key: item.Key,
117
+ size: item.Size || 0,
118
+ lastModified: item.LastModified || /* @__PURE__ */ new Date(),
119
+ etag: item.ETag
120
+ })),
121
+ nextCursor: response.NextContinuationToken
122
+ };
123
+ } catch (error) {
124
+ throw new EmDashStorageError("Failed to list files", "LIST_FAILED", error);
125
+ }
126
+ }
127
+ async getSignedUploadUrl(options) {
128
+ try {
129
+ const expiresIn = options.expiresIn || 3600;
130
+ const command = new PutObjectCommand({
131
+ Bucket: this.bucket,
132
+ Key: options.key,
133
+ ContentType: options.contentType,
134
+ ContentLength: options.size
135
+ });
136
+ const url = await getSignedUrl(this.client, command, { expiresIn });
137
+ const expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
138
+ return {
139
+ url,
140
+ method: "PUT",
141
+ headers: {
142
+ "Content-Type": options.contentType,
143
+ ...options.size ? { "Content-Length": String(options.size) } : {}
144
+ },
145
+ expiresAt
146
+ };
147
+ } catch (error) {
148
+ throw new EmDashStorageError(`Failed to generate signed URL for: ${options.key}`, "SIGNED_URL_FAILED", error);
149
+ }
150
+ }
151
+ getPublicUrl(key) {
152
+ if (this.publicUrl) return `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, "")}/${key}`;
153
+ return `${this.endpoint.replace(TRAILING_SLASH_PATTERN, "")}/${this.bucket}/${key}`;
154
+ }
155
+ };
156
+ /**
157
+ * Create S3 storage adapter
158
+ * This is the factory function called at runtime
159
+ */
160
+ function createStorage(config) {
161
+ const { endpoint, bucket, accessKeyId, secretAccessKey, region, publicUrl } = config;
162
+ if (typeof endpoint !== "string" || typeof bucket !== "string" || typeof accessKeyId !== "string" || typeof secretAccessKey !== "string") throw new Error("S3Storage requires 'endpoint', 'bucket', 'accessKeyId', and 'secretAccessKey' string config values");
163
+ return new S3Storage({
164
+ endpoint,
165
+ bucket,
166
+ accessKeyId,
167
+ secretAccessKey,
168
+ region: typeof region === "string" ? region : void 0,
169
+ publicUrl: typeof publicUrl === "string" ? publicUrl : void 0
170
+ });
171
+ }
172
+
173
+ //#endregion
174
+ export { S3Storage, createStorage };
175
+ //# sourceMappingURL=s3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.mjs","names":[],"sources":["../../src/storage/s3.ts"],"sourcesContent":["/**\n * S3-Compatible Storage Implementation\n *\n * Uses the AWS SDK v3 for S3 operations.\n * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.\n */\n\nimport {\n\tS3Client,\n\tPutObjectCommand,\n\tGetObjectCommand,\n\tDeleteObjectCommand,\n\tHeadObjectCommand,\n\tListObjectsV2Command,\n\ttype ListObjectsV2Response,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\n\nimport type {\n\tStorage,\n\tS3StorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/** Type guard for AWS SDK errors (have a `name` property) */\nfunction hasErrorName(error: unknown): error is Error & { name: string } {\n\treturn error instanceof Error && typeof error.name === \"string\";\n}\n\n/**\n * S3-compatible storage implementation\n */\nexport class S3Storage implements Storage {\n\tprivate client: S3Client;\n\tprivate bucket: string;\n\tprivate publicUrl?: string;\n\tprivate endpoint: string;\n\n\tconstructor(config: S3StorageConfig) {\n\t\tthis.bucket = config.bucket;\n\t\tthis.publicUrl = config.publicUrl;\n\t\tthis.endpoint = config.endpoint;\n\n\t\tthis.client = new S3Client({\n\t\t\tendpoint: config.endpoint,\n\t\t\tregion: config.region || \"auto\",\n\t\t\tcredentials: {\n\t\t\t\taccessKeyId: config.accessKeyId,\n\t\t\t\tsecretAccessKey: config.secretAccessKey,\n\t\t\t},\n\t\t\t// Required for R2 and some S3-compatible services\n\t\t\tforcePathStyle: true,\n\t\t});\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\t// Convert ReadableStream to Buffer if needed\n\t\t\tlet body: Buffer | Uint8Array;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbody = Buffer.concat(chunks);\n\t\t\t} else {\n\t\t\t\tbody = options.body;\n\t\t\t}\n\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: options.key,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: options.contentType,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: body.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to upload file: ${options.key}`,\n\t\t\t\t\"UPLOAD_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Body) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\t// Convert SDK stream to web ReadableStream\n\t\t\tconst body = response.Body.transformToWebStream();\n\n\t\t\treturn {\n\t\t\t\tbody,\n\t\t\t\tcontentType: response.ContentType || \"application/octet-stream\",\n\t\t\t\tsize: response.ContentLength || 0,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (\n\t\t\t\terror instanceof EmDashStorageError ||\n\t\t\t\t(hasErrorName(error) && error.name === \"NoSuchKey\")\n\t\t\t) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\", error);\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// S3 delete is idempotent, so we ignore \"not found\" errors\n\t\t\tif (!hasErrorName(error) || error.name !== \"NoSuchKey\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew HeadObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tif (hasErrorName(error) && error.name === \"NotFound\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to check file existence: ${key}`,\n\t\t\t\t\"HEAD_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- S3 client.send returns generic output; narrowing to ListObjectsV2Response\n\t\t\tconst response = (await this.client.send(\n\t\t\t\tnew ListObjectsV2Command({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tPrefix: options.prefix,\n\t\t\t\t\tMaxKeys: options.limit,\n\t\t\t\t\tContinuationToken: options.cursor,\n\t\t\t\t}),\n\t\t\t)) as ListObjectsV2Response;\n\n\t\t\treturn {\n\t\t\t\tfiles: (response.Contents || []).map(\n\t\t\t\t\t(item: { Key?: string; Size?: number; LastModified?: Date; ETag?: string }) => ({\n\t\t\t\t\t\tkey: item.Key!,\n\t\t\t\t\t\tsize: item.Size || 0,\n\t\t\t\t\t\tlastModified: item.LastModified || new Date(),\n\t\t\t\t\t\tetag: item.ETag,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tnextCursor: response.NextContinuationToken,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\ttry {\n\t\t\tconst expiresIn = options.expiresIn || 3600; // 1 hour default\n\n\t\t\tconst command = new PutObjectCommand({\n\t\t\t\tBucket: this.bucket,\n\t\t\t\tKey: options.key,\n\t\t\t\tContentType: options.contentType,\n\t\t\t\tContentLength: options.size,\n\t\t\t});\n\n\t\t\tconst url = await getSignedUrl(this.client, command, { expiresIn });\n\n\t\t\tconst expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tmethod: \"PUT\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": options.contentType,\n\t\t\t\t\t...(options.size ? { \"Content-Length\": String(options.size) } : {}),\n\t\t\t\t},\n\t\t\t\texpiresAt,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to generate signed URL for: ${options.key}`,\n\t\t\t\t\"SIGNED_URL_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\tif (this.publicUrl) {\n\t\t\treturn `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, \"\")}/${key}`;\n\t\t}\n\t\t// Default to endpoint + bucket + key\n\t\treturn `${this.endpoint.replace(TRAILING_SLASH_PATTERN, \"\")}/${this.bucket}/${key}`;\n\t}\n}\n\n/**\n * Create S3 storage adapter\n * This is the factory function called at runtime\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\tconst { endpoint, bucket, accessKeyId, secretAccessKey, region, publicUrl } = config;\n\tif (\n\t\ttypeof endpoint !== \"string\" ||\n\t\ttypeof bucket !== \"string\" ||\n\t\ttypeof accessKeyId !== \"string\" ||\n\t\ttypeof secretAccessKey !== \"string\"\n\t) {\n\t\tthrow new Error(\n\t\t\t\"S3Storage requires 'endpoint', 'bucket', 'accessKeyId', and 'secretAccessKey' string config values\",\n\t\t);\n\t}\n\treturn new S3Storage({\n\t\tendpoint,\n\t\tbucket,\n\t\taccessKeyId,\n\t\tsecretAccessKey,\n\t\tregion: typeof region === \"string\" ? region : undefined,\n\t\tpublicUrl: typeof publicUrl === \"string\" ? publicUrl : undefined,\n\t});\n}\n"],"mappings":"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"}