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
package/src/loader.ts ADDED
@@ -0,0 +1,770 @@
1
+ /**
2
+ * Astro Live Collections loader for EmDash
3
+ *
4
+ * This loader implements the Astro LiveLoader interface to fetch content
5
+ * at runtime from the database, enabling live editing without rebuilds.
6
+ *
7
+ * Architecture:
8
+ * - Single `_emdash` Astro collection handles all content types
9
+ * - Dialect comes from virtual module (configured in astro.config.mjs)
10
+ * - Each content type maps to its own database table: ec_posts, ec_products, etc.
11
+ * - `getEmDashCollection()` / `getEmDashEntry()` wrap Astro's live collection API
12
+ */
13
+
14
+ import type { LiveLoader } from "astro/loaders";
15
+ import { Kysely, sql, type Dialect } from "kysely";
16
+
17
+ import { currentTimestampValue, isPostgres } from "./database/dialect-helpers.js";
18
+ import { decodeCursor, encodeCursor } from "./database/repositories/types.js";
19
+ import type { Database } from "./index.js";
20
+ import { getRequestContext } from "./request-context.js";
21
+
22
+ const FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
23
+
24
+ /**
25
+ * System columns that are not part of the content data
26
+ */
27
+ /**
28
+ * System columns excluded from entry.data
29
+ * Note: slug is intentionally NOT excluded - it's useful as data.slug in templates
30
+ */
31
+ const SYSTEM_COLUMNS = new Set([
32
+ "id",
33
+ // "slug" - kept in data for template access
34
+ "status",
35
+ "author_id",
36
+ "primary_byline_id",
37
+ "created_at",
38
+ "updated_at",
39
+ "published_at",
40
+ "scheduled_at",
41
+ "deleted_at",
42
+ "version",
43
+ "live_revision_id",
44
+ "draft_revision_id",
45
+ "locale",
46
+ "translation_group",
47
+ ]);
48
+
49
+ /**
50
+ * Get the table name for a collection type
51
+ */
52
+ function getTableName(type: string): string {
53
+ return `ec_${type}`;
54
+ }
55
+
56
+ /**
57
+ * Cache for taxonomy names (only used for the primary database).
58
+ * Skipped when a per-request DB override is active (e.g. preview mode)
59
+ * because the override DB may have different taxonomies.
60
+ */
61
+ let taxonomyNames: Set<string> | null = null;
62
+
63
+ /**
64
+ * Get all taxonomy names (cached for primary DB, fresh for overrides)
65
+ */
66
+ async function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {
67
+ const hasDbOverride = !!getRequestContext()?.db;
68
+
69
+ if (!hasDbOverride && taxonomyNames) {
70
+ return taxonomyNames;
71
+ }
72
+
73
+ try {
74
+ const defs = await db.selectFrom("_emdash_taxonomy_defs").select("name").execute();
75
+ const names = new Set(defs.map((d) => d.name));
76
+ if (!hasDbOverride) {
77
+ taxonomyNames = names;
78
+ }
79
+ return names;
80
+ } catch {
81
+ // Table doesn't exist yet, return empty set
82
+ const empty = new Set<string>();
83
+ if (!hasDbOverride) {
84
+ taxonomyNames = empty;
85
+ }
86
+ return empty;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * System columns to include in data (mapped to camelCase where needed)
92
+ */
93
+ const INCLUDE_IN_DATA: Record<string, string> = {
94
+ id: "id",
95
+ status: "status",
96
+ author_id: "authorId",
97
+ primary_byline_id: "primaryBylineId",
98
+ created_at: "createdAt",
99
+ updated_at: "updatedAt",
100
+ published_at: "publishedAt",
101
+ scheduled_at: "scheduledAt",
102
+ draft_revision_id: "draftRevisionId",
103
+ live_revision_id: "liveRevisionId",
104
+ locale: "locale",
105
+ translation_group: "translationGroup",
106
+ };
107
+
108
+ /** System date columns that should be converted to Date objects */
109
+ const DATE_COLUMNS = new Set(["created_at", "updated_at", "published_at", "scheduled_at"]);
110
+
111
+ /** Safely extract a string value from a record, returning fallback if not a string */
112
+ function rowStr(row: Record<string, unknown>, key: string, fallback = ""): string {
113
+ const val = row[key];
114
+ return typeof val === "string" ? val : fallback;
115
+ }
116
+
117
+ /**
118
+ * Map a database row to entry data
119
+ * Extracts content fields (non-system columns) and parses JSON where needed.
120
+ * System columns needed for templates (id, status, dates) are included with camelCase names.
121
+ */
122
+ function mapRowToData(row: Record<string, unknown>): Record<string, unknown> {
123
+ const data: Record<string, unknown> = {};
124
+
125
+ for (const [key, value] of Object.entries(row)) {
126
+ // Include certain system columns (mapped to camelCase where needed)
127
+ if (key in INCLUDE_IN_DATA) {
128
+ // Convert date columns from ISO strings to Date objects
129
+ if (DATE_COLUMNS.has(key)) {
130
+ data[INCLUDE_IN_DATA[key]] = typeof value === "string" ? new Date(value) : null;
131
+ } else {
132
+ data[INCLUDE_IN_DATA[key]] = value;
133
+ }
134
+ continue;
135
+ }
136
+
137
+ if (SYSTEM_COLUMNS.has(key)) continue;
138
+
139
+ // Try to parse JSON strings (for portableText, json fields, etc.)
140
+ if (typeof value === "string") {
141
+ try {
142
+ // Only parse if it looks like JSON (starts with { or [)
143
+ if (value.startsWith("{") || value.startsWith("[")) {
144
+ data[key] = JSON.parse(value);
145
+ } else {
146
+ data[key] = value;
147
+ }
148
+ } catch {
149
+ data[key] = value;
150
+ }
151
+ } else {
152
+ data[key] = value;
153
+ }
154
+ }
155
+
156
+ return data;
157
+ }
158
+
159
+ /**
160
+ * Map revision data (already-parsed JSON object) to entry data.
161
+ * Strips _-prefixed metadata keys (e.g. _slug) used internally by revisions.
162
+ */
163
+ function mapRevisionData(data: Record<string, unknown>): Record<string, unknown> {
164
+ const result: Record<string, unknown> = {};
165
+ for (const [key, value] of Object.entries(data)) {
166
+ if (key.startsWith("_")) continue; // revision metadata
167
+ result[key] = value;
168
+ }
169
+ return result;
170
+ }
171
+
172
+ // Virtual module imports are lazy-loaded to avoid errors when importing
173
+ // emdash outside of Astro/Vite context (e.g., in astro.config.mjs)
174
+ let virtualConfig:
175
+ | {
176
+ database?: { config: unknown };
177
+ i18n?: { defaultLocale: string; locales: string[]; prefixDefaultLocale?: boolean } | null;
178
+ }
179
+ | undefined;
180
+ let virtualCreateDialect: ((config: unknown) => Dialect) | undefined;
181
+
182
+ async function loadVirtualModules() {
183
+ if (virtualConfig === undefined) {
184
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
185
+ // @ts-ignore - virtual module
186
+ const configModule = await import("virtual:emdash/config");
187
+ virtualConfig = configModule.default;
188
+ }
189
+ if (virtualCreateDialect === undefined) {
190
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
191
+ // @ts-ignore - virtual module
192
+ const dialectModule = await import("virtual:emdash/dialect");
193
+ virtualCreateDialect = dialectModule.createDialect;
194
+ // dialectType is no longer needed here — dialect detection is
195
+ // done via the db adapter instance in dialect-helpers.ts
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Entry data type - generic object
201
+ */
202
+ export type EntryData = Record<string, unknown>;
203
+
204
+ /**
205
+ * Sort direction
206
+ */
207
+ export type SortDirection = "asc" | "desc";
208
+
209
+ /**
210
+ * Order by specification - field name to direction
211
+ * @example { created_at: "desc" } - Sort by created_at descending
212
+ * @example { title: "asc" } - Sort by title ascending
213
+ */
214
+ export type OrderBySpec = Record<string, SortDirection>;
215
+
216
+ /**
217
+ * Build WHERE clause for status filtering.
218
+ * When filtering for 'published' status, also include scheduled content
219
+ * whose scheduled_at time has passed (treating it as effectively published).
220
+ */
221
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance
222
+ function buildStatusCondition(
223
+ db: Kysely<any>,
224
+ status: string,
225
+ tablePrefix?: string,
226
+ ): ReturnType<typeof sql> {
227
+ const statusField = tablePrefix ? `${tablePrefix}.status` : "status";
228
+ const scheduledAtField = tablePrefix ? `${tablePrefix}.scheduled_at` : "scheduled_at";
229
+
230
+ if (status === "published") {
231
+ // Include both published content AND scheduled content past its publish time.
232
+ // scheduled_at is stored as text (ISO 8601). On Postgres, we must cast it
233
+ // to timestamptz for the comparison with CURRENT_TIMESTAMP to work.
234
+ const scheduledAtExpr = isPostgres(db)
235
+ ? sql`${sql.ref(scheduledAtField)}::timestamptz`
236
+ : sql.ref(scheduledAtField);
237
+ return sql`(${sql.ref(statusField)} = 'published' OR (${sql.ref(statusField)} = 'scheduled' AND ${scheduledAtExpr} <= ${currentTimestampValue(db)}))`;
238
+ }
239
+
240
+ // For other statuses (draft, archived), just match exactly
241
+ return sql`${sql.ref(statusField)} = ${status}`;
242
+ }
243
+
244
+ /**
245
+ * Resolved primary sort field and direction (used for cursor pagination).
246
+ */
247
+ interface PrimarySort {
248
+ field: string;
249
+ direction: SortDirection;
250
+ }
251
+
252
+ /**
253
+ * Get the primary sort field from an orderBy spec (first valid field, or default).
254
+ */
255
+ function getPrimarySort(orderBy: OrderBySpec | undefined, tablePrefix?: string): PrimarySort {
256
+ if (orderBy) {
257
+ for (const [field, direction] of Object.entries(orderBy)) {
258
+ if (FIELD_NAME_PATTERN.test(field)) {
259
+ const fullField = tablePrefix ? `${tablePrefix}.${field}` : field;
260
+ return { field: fullField, direction };
261
+ }
262
+ }
263
+ }
264
+ const defaultField = tablePrefix ? `${tablePrefix}.created_at` : "created_at";
265
+ return { field: defaultField, direction: "desc" };
266
+ }
267
+
268
+ /**
269
+ * Build ORDER BY clause from orderBy spec
270
+ * Validates field names to prevent SQL injection (alphanumeric + underscore only)
271
+ * Supports multiple sort fields in object key order
272
+ */
273
+ function buildOrderByClause(
274
+ orderBy: OrderBySpec | undefined,
275
+ tablePrefix?: string,
276
+ ): ReturnType<typeof sql> {
277
+ // Default to created_at DESC
278
+ if (!orderBy || Object.keys(orderBy).length === 0) {
279
+ const field = tablePrefix ? `${tablePrefix}.created_at` : "created_at";
280
+ return sql`ORDER BY ${sql.ref(field)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : "id")} DESC`;
281
+ }
282
+
283
+ const sortParts: ReturnType<typeof sql>[] = [];
284
+
285
+ for (const [field, direction] of Object.entries(orderBy)) {
286
+ // Validate field name (alphanumeric + underscore only)
287
+ if (!FIELD_NAME_PATTERN.test(field)) {
288
+ continue; // Skip invalid field names
289
+ }
290
+
291
+ const fullField = tablePrefix ? `${tablePrefix}.${field}` : field;
292
+ const dir = direction === "asc" ? sql`ASC` : sql`DESC`;
293
+ sortParts.push(sql`${sql.ref(fullField)} ${dir}`);
294
+ }
295
+
296
+ // If no valid sort fields, fall back to default
297
+ if (sortParts.length === 0) {
298
+ const defaultField = tablePrefix ? `${tablePrefix}.created_at` : "created_at";
299
+ return sql`ORDER BY ${sql.ref(defaultField)} DESC, ${sql.ref(tablePrefix ? `${tablePrefix}.id` : "id")} DESC`;
300
+ }
301
+
302
+ // Add id as tiebreaker to ensure stable cursor ordering
303
+ const primary = getPrimarySort(orderBy, tablePrefix);
304
+ const idField = tablePrefix ? `${tablePrefix}.id` : "id";
305
+ const idDir = primary.direction === "asc" ? sql`ASC` : sql`DESC`;
306
+ sortParts.push(sql`${sql.ref(idField)} ${idDir}`);
307
+
308
+ return sql`ORDER BY ${sql.join(sortParts, sql`, `)}`;
309
+ }
310
+
311
+ /**
312
+ * Build a cursor WHERE condition for keyset pagination.
313
+ * Uses the primary sort field + id as tiebreaker for stable ordering.
314
+ */
315
+ function buildCursorCondition(
316
+ cursor: string,
317
+ orderBy: OrderBySpec | undefined,
318
+ tablePrefix?: string,
319
+ ): ReturnType<typeof sql> | null {
320
+ const decoded = decodeCursor(cursor);
321
+ if (!decoded) return null;
322
+
323
+ const { orderValue, id: cursorId } = decoded;
324
+ const primary = getPrimarySort(orderBy, tablePrefix);
325
+ const idField = tablePrefix ? `${tablePrefix}.id` : "id";
326
+
327
+ if (primary.direction === "desc") {
328
+ return sql`(${sql.ref(primary.field)} < ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} < ${cursorId}))`;
329
+ }
330
+ return sql`(${sql.ref(primary.field)} > ${orderValue} OR (${sql.ref(primary.field)} = ${orderValue} AND ${sql.ref(idField)} > ${cursorId}))`;
331
+ }
332
+
333
+ /**
334
+ * Filter for loadCollection - type is required
335
+ */
336
+ export interface CollectionFilter {
337
+ type: string;
338
+ status?: "draft" | "published" | "archived";
339
+ limit?: number;
340
+ /**
341
+ * Opaque cursor for keyset pagination.
342
+ * Pass the `nextCursor` value from a previous result to fetch the next page.
343
+ */
344
+ cursor?: string;
345
+ /**
346
+ * Filter by field values or taxonomy terms
347
+ */
348
+ where?: Record<string, string | string[]>;
349
+ /**
350
+ * Order results by field(s)
351
+ * @default { created_at: "desc" }
352
+ */
353
+ orderBy?: OrderBySpec;
354
+ /**
355
+ * Filter by locale (e.g. 'en', 'fr').
356
+ * When set, only returns content in this locale.
357
+ */
358
+ locale?: string;
359
+ }
360
+
361
+ /**
362
+ * Filter for loadEntry - type and id are required
363
+ */
364
+ export interface EntryFilter {
365
+ type: string;
366
+ id: string;
367
+ /**
368
+ * When set, fetch content data from this revision instead of the content table.
369
+ * Used by preview mode to serve draft revision data.
370
+ */
371
+ revisionId?: string;
372
+ /**
373
+ * Locale to scope slug lookup. Only affects slug resolution;
374
+ * IDs are globally unique and always resolve regardless of locale.
375
+ */
376
+ locale?: string;
377
+ }
378
+
379
+ // Cached database instance (shared across calls)
380
+ let dbInstance: Kysely<Database> | null = null;
381
+
382
+ /**
383
+ * Get the database instance. Used by query wrapper functions and middleware.
384
+ *
385
+ * Checks the ALS request context first — if a per-request DB override is set
386
+ * (e.g. by DO preview middleware), it takes precedence over the module-level
387
+ * cached instance. This allows preview mode to route queries to an isolated
388
+ * Durable Object database without modifying any calling code.
389
+ *
390
+ * Initializes the default database on first call using config from virtual module.
391
+ */
392
+ export async function getDb(): Promise<Kysely<Database>> {
393
+ // Per-request DB override via ALS (normal mode)
394
+ const ctx = getRequestContext();
395
+ if (ctx?.db) {
396
+ return ctx.db as Kysely<Database>; // eslint-disable-line typescript-eslint(no-unsafe-type-assertion) -- db is typed as unknown in RequestContext to avoid circular deps
397
+ }
398
+
399
+ if (!dbInstance) {
400
+ await loadVirtualModules();
401
+ if (!virtualConfig?.database || typeof virtualCreateDialect !== "function") {
402
+ throw new Error(
403
+ "EmDash database not configured. Add database config to emdash() in astro.config.mjs",
404
+ );
405
+ }
406
+ const dialect = virtualCreateDialect(virtualConfig.database.config);
407
+ dbInstance = new Kysely<Database>({ dialect });
408
+ }
409
+ return dbInstance;
410
+ }
411
+
412
+ /**
413
+ * Create an EmDash Live Collections loader
414
+ *
415
+ * This loader handles ALL content types in a single Astro collection.
416
+ * Use `getEmDashCollection()` and `getEmDashEntry()` to query
417
+ * specific content types.
418
+ *
419
+ * Database is configured in astro.config.mjs via the emdash() integration.
420
+ *
421
+ * @example
422
+ * ```ts
423
+ * // src/live.config.ts
424
+ * import { defineLiveCollection } from "astro:content";
425
+ * import { emdashLoader } from "emdash";
426
+ *
427
+ * export const collections = {
428
+ * emdash: defineLiveCollection({
429
+ * loader: emdashLoader(),
430
+ * }),
431
+ * };
432
+ * ```
433
+ */
434
+ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFilter> {
435
+ return {
436
+ name: "emdash",
437
+
438
+ /**
439
+ * Load all entries for a content type
440
+ */
441
+ async loadCollection({ filter }) {
442
+ try {
443
+ // Get DB instance (initializes on first use)
444
+ const db = await getDb();
445
+
446
+ // Type filter is required
447
+ const type = filter?.type;
448
+ if (!type) {
449
+ return {
450
+ error: new Error(
451
+ "type filter is required. Use getEmDashCollection() instead of getLiveCollection() directly.",
452
+ ),
453
+ };
454
+ }
455
+
456
+ // Query the per-collection table (ec_posts, ec_products, etc.)
457
+ const tableName = getTableName(type);
458
+
459
+ // Build query with dynamic table name
460
+ const status = filter?.status || "published";
461
+ const limit = filter?.limit;
462
+ const cursor = filter?.cursor;
463
+ const where = filter?.where;
464
+ const orderBy = filter?.orderBy;
465
+ const locale = filter?.locale;
466
+
467
+ // Cursor pagination: over-fetch by 1 to detect next page
468
+ const fetchLimit = limit ? limit + 1 : undefined;
469
+
470
+ // Build cursor condition if cursor is provided
471
+ const cursorCondition = cursor ? buildCursorCondition(cursor, orderBy) : null;
472
+ const cursorConditionPrefixed = cursor
473
+ ? buildCursorCondition(cursor, orderBy, tableName)
474
+ : null;
475
+
476
+ // Check if we need taxonomy filtering
477
+ let result: { rows: Record<string, unknown>[] };
478
+
479
+ if (where && Object.keys(where).length > 0) {
480
+ // Get taxonomy names to detect taxonomy filters
481
+ const taxNames = await getTaxonomyNames(db);
482
+ const taxonomyFilters: Record<string, string | string[]> = {};
483
+
484
+ for (const [key, value] of Object.entries(where)) {
485
+ if (taxNames.has(key)) {
486
+ taxonomyFilters[key] = value;
487
+ }
488
+ }
489
+
490
+ // If we have taxonomy filters, use JOIN
491
+ if (Object.keys(taxonomyFilters).length > 0) {
492
+ // Build query with taxonomy JOIN
493
+ // For now, support single taxonomy filter (can extend later for multiple)
494
+ const [taxName, termSlugs] = Object.entries(taxonomyFilters)[0];
495
+ const slugs = Array.isArray(termSlugs) ? termSlugs : [termSlugs];
496
+ const orderByClause = buildOrderByClause(orderBy, tableName);
497
+
498
+ const statusCondition = buildStatusCondition(db, status, tableName);
499
+ const localeCondition = locale
500
+ ? sql`AND ${sql.ref(tableName)}.locale = ${locale}`
501
+ : sql``;
502
+ const cursorCond = cursorConditionPrefixed
503
+ ? sql`AND ${cursorConditionPrefixed}`
504
+ : sql``;
505
+ result = await sql<Record<string, unknown>>`
506
+ SELECT DISTINCT ${sql.ref(tableName)}.* FROM ${sql.ref(tableName)}
507
+ INNER JOIN content_taxonomies ct
508
+ ON ct.collection = ${type}
509
+ AND ct.entry_id = ${sql.ref(tableName)}.id
510
+ INNER JOIN taxonomies t
511
+ ON t.id = ct.taxonomy_id
512
+ WHERE ${sql.ref(tableName)}.deleted_at IS NULL
513
+ AND ${statusCondition}
514
+ ${localeCondition}
515
+ ${cursorCond}
516
+ AND t.name = ${taxName}
517
+ AND t.slug IN (${sql.join(slugs.map((s) => sql`${s}`))})
518
+ ${orderByClause}
519
+ ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
520
+ `.execute(db);
521
+ } else {
522
+ // No taxonomy filters, use simple query
523
+ const orderByClause = buildOrderByClause(orderBy);
524
+ const statusCondition = buildStatusCondition(db, status);
525
+ const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
526
+ const cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;
527
+ result = await sql<Record<string, unknown>>`
528
+ SELECT * FROM ${sql.ref(tableName)}
529
+ WHERE deleted_at IS NULL
530
+ AND ${statusCondition}
531
+ ${localeFilter}
532
+ ${cursorCond}
533
+ ${orderByClause}
534
+ ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
535
+ `.execute(db);
536
+ }
537
+ } else {
538
+ // No where clause, use simple query
539
+ const orderByClause = buildOrderByClause(orderBy);
540
+ const statusCondition = buildStatusCondition(db, status);
541
+ const localeFilter = locale ? sql`AND locale = ${locale}` : sql``;
542
+ const cursorCond = cursorCondition ? sql`AND ${cursorCondition}` : sql``;
543
+ result = await sql<Record<string, unknown>>`
544
+ SELECT * FROM ${sql.ref(tableName)}
545
+ WHERE deleted_at IS NULL
546
+ AND ${statusCondition}
547
+ ${localeFilter}
548
+ ${cursorCond}
549
+ ${orderByClause}
550
+ ${fetchLimit ? sql`LIMIT ${fetchLimit}` : sql``}
551
+ `.execute(db);
552
+ }
553
+
554
+ // Detect whether there are more results (over-fetched by 1)
555
+ const hasMore = limit ? result.rows.length > limit : false;
556
+ const rows = hasMore ? result.rows.slice(0, limit) : result.rows;
557
+
558
+ // Map rows to entries
559
+ const i18nConfig = virtualConfig?.i18n;
560
+ const i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;
561
+ const entries = rows.map((row) => {
562
+ const slug = rowStr(row, "slug") || rowStr(row, "id");
563
+ const rowLocale = rowStr(row, "locale");
564
+ const shouldPrefix =
565
+ i18nEnabled &&
566
+ rowLocale !== "" &&
567
+ (rowLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
568
+ const id = shouldPrefix ? `${rowLocale}/${slug}` : slug;
569
+ return {
570
+ id,
571
+ slug: rowStr(row, "slug"),
572
+ status: rowStr(row, "status", "draft"),
573
+ data: mapRowToData(row),
574
+ cacheHint: {
575
+ tags: [rowStr(row, "id")],
576
+ lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
577
+ },
578
+ };
579
+ });
580
+
581
+ // Encode nextCursor from the last row if there are more results
582
+ let nextCursor: string | undefined;
583
+ if (hasMore && rows.length > 0) {
584
+ const lastRow = rows.at(-1)!;
585
+ const primary = getPrimarySort(orderBy);
586
+ // Strip table prefix from field name for row lookup
587
+ const fieldName = primary.field.includes(".")
588
+ ? primary.field.split(".").pop()!
589
+ : primary.field;
590
+ const lastOrderValue = lastRow[fieldName];
591
+ const orderStr =
592
+ typeof lastOrderValue === "string" || typeof lastOrderValue === "number"
593
+ ? String(lastOrderValue)
594
+ : "";
595
+ nextCursor = encodeCursor(orderStr, String(lastRow.id));
596
+ }
597
+
598
+ // Collection-level cache hint uses the most recent updated_at
599
+ let collectionLastModified: Date | undefined;
600
+ for (const row of rows) {
601
+ if (row.updated_at) {
602
+ const d = new Date(rowStr(row, "updated_at"));
603
+ if (!collectionLastModified || d > collectionLastModified) {
604
+ collectionLastModified = d;
605
+ }
606
+ }
607
+ }
608
+
609
+ return {
610
+ entries,
611
+ nextCursor,
612
+ cacheHint: {
613
+ tags: [type],
614
+ lastModified: collectionLastModified,
615
+ },
616
+ };
617
+ } catch (error) {
618
+ // Handle missing table gracefully - return empty collection
619
+ // This happens before migrations have run
620
+ const message = error instanceof Error ? error.message : String(error);
621
+ const lowerMessage = message.toLowerCase();
622
+ if (
623
+ lowerMessage.includes("no such table") ||
624
+ (lowerMessage.includes("table") && lowerMessage.includes("does not exist")) ||
625
+ (lowerMessage.includes("relation") && lowerMessage.includes("does not exist"))
626
+ ) {
627
+ return { entries: [] };
628
+ }
629
+
630
+ return {
631
+ error: new Error(`Failed to load collection: ${message}`),
632
+ };
633
+ }
634
+ },
635
+
636
+ /**
637
+ * Load a single entry by type and ID/slug
638
+ *
639
+ * When filter.revisionId is set (preview mode), the entry's data
640
+ * comes from the revisions table instead of the content table columns.
641
+ */
642
+ async loadEntry({ filter }) {
643
+ try {
644
+ // Get DB instance
645
+ const db = await getDb();
646
+
647
+ // Both type and id are required
648
+ const type = filter?.type;
649
+ const id = filter?.id;
650
+
651
+ if (!type || !id) {
652
+ return {
653
+ error: new Error(
654
+ "type and id filters are required. Use getEmDashEntry() instead of getLiveEntry() directly.",
655
+ ),
656
+ };
657
+ }
658
+
659
+ // Query the per-collection table
660
+ const tableName = getTableName(type);
661
+ const locale = filter?.locale;
662
+
663
+ // Use raw SQL for dynamic table name, match by slug or id
664
+ // When locale is specified, prefer locale-scoped slug match,
665
+ // but IDs are globally unique so always check id without locale scope
666
+ const result = locale
667
+ ? await sql<Record<string, unknown>>`
668
+ SELECT * FROM ${sql.ref(tableName)}
669
+ WHERE deleted_at IS NULL
670
+ AND ((slug = ${id} AND locale = ${locale}) OR id = ${id})
671
+ LIMIT 1
672
+ `.execute(db)
673
+ : await sql<Record<string, unknown>>`
674
+ SELECT * FROM ${sql.ref(tableName)}
675
+ WHERE deleted_at IS NULL
676
+ AND (slug = ${id} OR id = ${id})
677
+ LIMIT 1
678
+ `.execute(db);
679
+
680
+ const row = result.rows[0];
681
+ if (!row) {
682
+ return undefined;
683
+ }
684
+
685
+ const i18nConfig = virtualConfig?.i18n;
686
+ const i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;
687
+ const entrySlug = rowStr(row, "slug") || rowStr(row, "id");
688
+ const entryLocale = rowStr(row, "locale");
689
+ const shouldPrefixEntry =
690
+ i18nEnabled &&
691
+ entryLocale !== "" &&
692
+ (entryLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
693
+ const entryId = shouldPrefixEntry ? `${entryLocale}/${entrySlug}` : entrySlug;
694
+
695
+ // Preview mode: override content fields with revision data,
696
+ // keeping system metadata from the content table row.
697
+ const revisionId = filter?.revisionId;
698
+ if (revisionId) {
699
+ const revRow = await sql<{ data: string }>`
700
+ SELECT data FROM revisions
701
+ WHERE id = ${revisionId}
702
+ LIMIT 1
703
+ `.execute(db);
704
+
705
+ const revData = revRow.rows[0];
706
+ if (revData) {
707
+ const parsed: Record<string, unknown> = JSON.parse(revData.data);
708
+ // System metadata from content table + content fields from revision
709
+ const systemData: Record<string, unknown> = {};
710
+ for (const [key, mappedKey] of Object.entries(INCLUDE_IN_DATA)) {
711
+ if (key in row) {
712
+ if (DATE_COLUMNS.has(key)) {
713
+ systemData[mappedKey] = typeof row[key] === "string" ? new Date(row[key]) : null;
714
+ } else {
715
+ systemData[mappedKey] = row[key];
716
+ }
717
+ }
718
+ }
719
+ // Use slug from revision metadata if present, else from content table
720
+ const slug = typeof parsed._slug === "string" ? parsed._slug : rowStr(row, "slug");
721
+ const revSlug = slug || rowStr(row, "id");
722
+ const revLocale = rowStr(row, "locale");
723
+ const shouldPrefixRev =
724
+ i18nEnabled &&
725
+ revLocale !== "" &&
726
+ (revLocale !== i18nConfig.defaultLocale || i18nConfig.prefixDefaultLocale);
727
+ const revId = shouldPrefixRev ? `${revLocale}/${revSlug}` : revSlug;
728
+ return {
729
+ id: revId,
730
+ slug,
731
+ status: rowStr(row, "status", "draft"),
732
+ data: { ...systemData, slug, ...mapRevisionData(parsed) },
733
+ cacheHint: {
734
+ tags: [rowStr(row, "id")],
735
+ lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
736
+ },
737
+ };
738
+ }
739
+ }
740
+
741
+ return {
742
+ id: entryId,
743
+ slug: rowStr(row, "slug"),
744
+ status: rowStr(row, "status", "draft"),
745
+ data: mapRowToData(row),
746
+ cacheHint: {
747
+ tags: [rowStr(row, "id")],
748
+ lastModified: row.updated_at ? new Date(rowStr(row, "updated_at")) : undefined,
749
+ },
750
+ };
751
+ } catch (error) {
752
+ // Handle missing table gracefully - return undefined (not found)
753
+ // This happens before migrations have run
754
+ const message = error instanceof Error ? error.message : String(error);
755
+ const lowerMessage = message.toLowerCase();
756
+ if (
757
+ lowerMessage.includes("no such table") ||
758
+ (lowerMessage.includes("table") && lowerMessage.includes("does not exist")) ||
759
+ (lowerMessage.includes("relation") && lowerMessage.includes("does not exist"))
760
+ ) {
761
+ return undefined;
762
+ }
763
+
764
+ return {
765
+ error: new Error(`Failed to load entry: ${message}`),
766
+ };
767
+ }
768
+ },
769
+ };
770
+ }