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,396 @@
1
+ /**
2
+ * Search Query Functions
3
+ *
4
+ * Programmatic API for searching content using FTS5.
5
+ */
6
+
7
+ import type { Kysely } from "kysely";
8
+ import { sql } from "kysely";
9
+
10
+ import type { Database } from "../database/types.js";
11
+ import { validateIdentifier } from "../database/validate.js";
12
+ import { getDb } from "../loader.js";
13
+ import { FTSManager } from "./fts-manager.js";
14
+ import type {
15
+ SearchOptions,
16
+ CollectionSearchOptions,
17
+ SearchResult,
18
+ SearchResponse,
19
+ SuggestOptions,
20
+ Suggestion,
21
+ SearchStats,
22
+ } from "./types.js";
23
+
24
+ /** Pattern to split on whitespace for query term extraction */
25
+ const WHITESPACE_SPLIT_PATTERN = /\s+/;
26
+ const FTS_OPERATORS_PATTERN = /\b(AND|OR|NOT|NEAR)\b/i;
27
+ const DOUBLE_QUOTE_PATTERN = /"/g;
28
+
29
+ /**
30
+ * Search across multiple collections
31
+ *
32
+ * Public API that auto-injects the database.
33
+ *
34
+ * @param query - Search query (FTS5 syntax supported)
35
+ * @param options - Search options
36
+ * @returns Search results with pagination
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { search } from "emdash";
41
+ *
42
+ * const results = await search("hello world", {
43
+ * collections: ["posts", "pages"],
44
+ * limit: 20
45
+ * });
46
+ * ```
47
+ */
48
+ export async function search(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
49
+ const db = await getDb();
50
+ return searchWithDb(db, query, options);
51
+ }
52
+
53
+ /**
54
+ * Search across multiple collections (with explicit db)
55
+ *
56
+ * @internal Use `search()` in templates. This variant is for admin routes
57
+ * that already have a database handle.
58
+ *
59
+ * @param db - Kysely database instance
60
+ * @param query - Search query (FTS5 syntax supported)
61
+ * @param options - Search options
62
+ * @returns Search results with pagination
63
+ */
64
+ export async function searchWithDb(
65
+ db: Kysely<Database>,
66
+ query: string,
67
+ options: SearchOptions = {},
68
+ ): Promise<SearchResponse> {
69
+ const ftsManager = new FTSManager(db);
70
+ const limit = options.limit ?? 20;
71
+ const status = options.status ?? "published";
72
+
73
+ // Get searchable collections
74
+ let collections = options.collections;
75
+ if (!collections || collections.length === 0) {
76
+ collections = await getSearchableCollections(db);
77
+ }
78
+
79
+ if (collections.length === 0) {
80
+ return { items: [] };
81
+ }
82
+
83
+ // Search each collection and merge results
84
+ const allResults: SearchResult[] = [];
85
+
86
+ for (const collection of collections) {
87
+ const config = await ftsManager.getSearchConfig(collection);
88
+ if (!config?.enabled) {
89
+ continue;
90
+ }
91
+
92
+ const collectionResults = await searchSingleCollection(
93
+ db,
94
+ collection,
95
+ query,
96
+ {
97
+ status,
98
+ locale: options.locale,
99
+ limit: limit * 2, // Get extra for merging
100
+ },
101
+ config.weights,
102
+ );
103
+
104
+ allResults.push(...collectionResults);
105
+ }
106
+
107
+ // Sort by score descending
108
+ allResults.sort((a, b) => b.score - a.score);
109
+
110
+ // Apply limit
111
+ const items = allResults.slice(0, limit);
112
+
113
+ return { items };
114
+ }
115
+
116
+ /**
117
+ * Search within a single collection
118
+ *
119
+ * @param db - Kysely database instance
120
+ * @param collection - Collection slug
121
+ * @param query - Search query (FTS5 syntax supported)
122
+ * @param options - Search options
123
+ * @returns Search results with pagination
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const results = await searchCollection(db, "posts", "hello world", {
128
+ * limit: 10
129
+ * });
130
+ * ```
131
+ */
132
+ export async function searchCollection(
133
+ db: Kysely<Database>,
134
+ collection: string,
135
+ query: string,
136
+ options: CollectionSearchOptions = {},
137
+ ): Promise<SearchResponse> {
138
+ const ftsManager = new FTSManager(db);
139
+ const config = await ftsManager.getSearchConfig(collection);
140
+
141
+ if (!config?.enabled) {
142
+ return { items: [] };
143
+ }
144
+
145
+ const items = await searchSingleCollection(db, collection, query, options, config.weights);
146
+
147
+ return { items };
148
+ }
149
+
150
+ /**
151
+ * Internal function to search a single collection
152
+ */
153
+ async function searchSingleCollection(
154
+ db: Kysely<Database>,
155
+ collection: string,
156
+ query: string,
157
+ options: CollectionSearchOptions,
158
+ weights?: Record<string, number>,
159
+ ): Promise<SearchResult[]> {
160
+ // Validate before any raw SQL interpolation
161
+ validateIdentifier(collection, "collection slug");
162
+
163
+ const ftsManager = new FTSManager(db);
164
+ const ftsTable = ftsManager.getFtsTableName(collection);
165
+ const contentTable = ftsManager.getContentTableName(collection);
166
+ const limit = options.limit ?? 20;
167
+ const status = options.status ?? "published";
168
+ const locale = options.locale;
169
+
170
+ // Check if FTS table exists
171
+ if (!(await ftsManager.ftsTableExists(collection))) {
172
+ return [];
173
+ }
174
+
175
+ // Escape the query for FTS5
176
+ const escapedQuery = escapeQuery(query);
177
+ if (!escapedQuery) {
178
+ return [];
179
+ }
180
+
181
+ // Get searchable fields for snippet generation
182
+ const searchableFields = await ftsManager.getSearchableFields(collection);
183
+
184
+ // Build weight string for bm25 if weights provided
185
+ // Format: bm25(table, weight1, weight2, ...)
186
+ // First two weights are for 'id' and 'locale' columns (UNINDEXED, so 0)
187
+ let bm25Args = "";
188
+ if (weights && searchableFields.length > 0) {
189
+ const weightValues = ["0", "0"]; // id column, locale column
190
+ for (const field of searchableFields) {
191
+ weightValues.push(String(weights[field] ?? 1));
192
+ }
193
+ bm25Args = weightValues.join(", ");
194
+ }
195
+
196
+ // Build and execute the search query
197
+ // Using raw SQL because Kysely doesn't have FTS5 support
198
+ const bm25Expr = bm25Args ? `bm25("${ftsTable}", ${bm25Args})` : `bm25("${ftsTable}")`;
199
+
200
+ // Snippet column index is 2 (after id=0, locale=1, first searchable field=2)
201
+ const results = await sql<{
202
+ id: string;
203
+ slug: string | null;
204
+ locale: string;
205
+ title: string | null;
206
+ snippet: string;
207
+ score: number;
208
+ }>`
209
+ SELECT
210
+ c.id,
211
+ c.slug,
212
+ c.locale,
213
+ c.title,
214
+ snippet("${sql.raw(ftsTable)}", 2, '<mark>', '</mark>', '...', 32) as snippet,
215
+ ${sql.raw(bm25Expr)} as score
216
+ FROM "${sql.raw(ftsTable)}" f
217
+ JOIN "${sql.raw(contentTable)}" c ON f.id = c.id
218
+ WHERE "${sql.raw(ftsTable)}" MATCH ${escapedQuery}
219
+ AND c.status = ${status}
220
+ AND c.deleted_at IS NULL
221
+ ${locale ? sql`AND c.locale = ${locale}` : sql``}
222
+ ORDER BY score
223
+ LIMIT ${limit}
224
+ `.execute(db);
225
+
226
+ return results.rows.map((row) => ({
227
+ collection,
228
+ id: row.id,
229
+ slug: row.slug,
230
+ locale: row.locale,
231
+ title: row.title ?? undefined,
232
+ snippet: row.snippet,
233
+ score: Math.abs(row.score), // bm25 returns negative scores
234
+ }));
235
+ }
236
+
237
+ /**
238
+ * Get search suggestions for autocomplete
239
+ *
240
+ * @param db - Kysely database instance
241
+ * @param query - Partial search query
242
+ * @param options - Suggestion options
243
+ * @returns Array of suggestions
244
+ */
245
+ export async function getSuggestions(
246
+ db: Kysely<Database>,
247
+ query: string,
248
+ options: SuggestOptions = {},
249
+ ): Promise<Suggestion[]> {
250
+ const limit = options.limit ?? 5;
251
+ const locale = options.locale;
252
+
253
+ // Get searchable collections
254
+ let collections = options.collections;
255
+ if (!collections || collections.length === 0) {
256
+ collections = await getSearchableCollections(db);
257
+ }
258
+
259
+ if (collections.length === 0) {
260
+ return [];
261
+ }
262
+
263
+ const suggestions: Suggestion[] = [];
264
+
265
+ for (const collection of collections) {
266
+ const ftsManager = new FTSManager(db);
267
+ const config = await ftsManager.getSearchConfig(collection);
268
+ if (!config?.enabled) {
269
+ continue;
270
+ }
271
+
272
+ // Validate before raw SQL interpolation
273
+ validateIdentifier(collection, "collection slug");
274
+
275
+ const ftsTable = ftsManager.getFtsTableName(collection);
276
+ const contentTable = ftsManager.getContentTableName(collection);
277
+
278
+ // Use prefix search for autocomplete
279
+ const prefixQuery = `${escapeQuery(query)}*`;
280
+ if (!prefixQuery || prefixQuery === "*") {
281
+ continue;
282
+ }
283
+
284
+ const results = await sql<{
285
+ id: string;
286
+ title: string;
287
+ }>`
288
+ SELECT
289
+ c.id,
290
+ c.title
291
+ FROM "${sql.raw(ftsTable)}" f
292
+ JOIN "${sql.raw(contentTable)}" c ON f.id = c.id
293
+ WHERE "${sql.raw(ftsTable)}" MATCH ${prefixQuery}
294
+ AND c.status = 'published'
295
+ AND c.deleted_at IS NULL
296
+ AND c.title IS NOT NULL
297
+ ${locale ? sql`AND c.locale = ${locale}` : sql``}
298
+ ORDER BY bm25("${sql.raw(ftsTable)}")
299
+ LIMIT ${limit}
300
+ `.execute(db);
301
+
302
+ for (const row of results.rows) {
303
+ suggestions.push({
304
+ collection,
305
+ id: row.id,
306
+ title: row.title,
307
+ });
308
+ }
309
+ }
310
+
311
+ return suggestions.slice(0, limit);
312
+ }
313
+
314
+ /**
315
+ * Get search statistics for all collections
316
+ */
317
+ export async function getSearchStats(db: Kysely<Database>): Promise<SearchStats> {
318
+ const ftsManager = new FTSManager(db);
319
+ const collections = await getSearchableCollections(db);
320
+ const stats: SearchStats = { collections: {} };
321
+
322
+ for (const collection of collections) {
323
+ const collectionStats = await ftsManager.getIndexStats(collection);
324
+ if (collectionStats) {
325
+ stats.collections[collection] = collectionStats;
326
+ }
327
+ }
328
+
329
+ return stats;
330
+ }
331
+
332
+ /**
333
+ * Get list of collections with search enabled
334
+ */
335
+ async function getSearchableCollections(db: Kysely<Database>): Promise<string[]> {
336
+ const results = await db
337
+ .selectFrom("_emdash_collections")
338
+ .select(["slug", "search_config"])
339
+ .execute();
340
+
341
+ return results
342
+ .filter((r) => {
343
+ if (!r.search_config) return false;
344
+ try {
345
+ const config = JSON.parse(r.search_config);
346
+ return config.enabled === true;
347
+ } catch {
348
+ return false;
349
+ }
350
+ })
351
+ .map((r) => r.slug);
352
+ }
353
+
354
+ /**
355
+ * Escape a query string for FTS5
356
+ *
357
+ * Handles special characters and prevents injection.
358
+ */
359
+ function escapeQuery(query: string): string {
360
+ if (!query || typeof query !== "string") {
361
+ return "";
362
+ }
363
+
364
+ // Trim whitespace
365
+ query = query.trim();
366
+
367
+ if (query.length === 0) {
368
+ return "";
369
+ }
370
+
371
+ // FTS5 special characters that need escaping in terms: " * ^
372
+ // We'll wrap terms in quotes to handle most cases
373
+ // But first, escape any existing quotes
374
+ const escaped = query.replace(DOUBLE_QUOTE_PATTERN, '""');
375
+
376
+ // If the query contains FTS5 operators (AND, OR, NOT, NEAR),
377
+ // pass through as-is (user knows what they're doing)
378
+ if (FTS_OPERATORS_PATTERN.test(query)) {
379
+ return escaped;
380
+ }
381
+
382
+ // If already quoted, pass through
383
+ if (query.startsWith('"') && query.endsWith('"')) {
384
+ return query;
385
+ }
386
+
387
+ // For simple queries, wrap each word to handle special chars
388
+ const terms = escaped.split(WHITESPACE_SPLIT_PATTERN).filter((t) => t.length > 0);
389
+ if (terms.length === 0) {
390
+ return "";
391
+ }
392
+
393
+ // Join with implicit AND, add prefix matching (*) to all terms
394
+ // This allows "hel wor" to match "hello world"
395
+ return terms.map((t) => `"${t}"*`).join(" ");
396
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Text Extraction
3
+ *
4
+ * Extracts plain text from Portable Text blocks for FTS indexing.
5
+ * Uses @portabletext/toolkit as base with extensions for custom block types.
6
+ */
7
+
8
+ import { toPlainText } from "@portabletext/toolkit";
9
+
10
+ import type { PortableTextBlock } from "../content/converters/types.js";
11
+
12
+ /**
13
+ * Validate that a value looks like a Portable Text block array.
14
+ * Each element must have at least a `_type` string property.
15
+ */
16
+ function isPortableTextArray(value: unknown[]): value is PortableTextBlock[] {
17
+ return value.every(
18
+ (item) =>
19
+ typeof item === "object" &&
20
+ item !== null &&
21
+ "_type" in item &&
22
+ typeof item._type === "string",
23
+ );
24
+ }
25
+
26
+ /**
27
+ * Extract additional text from custom block types that toPlainText doesn't handle
28
+ */
29
+ function extractCustomBlockText(block: PortableTextBlock): string {
30
+ // Code blocks - include the code content
31
+ if (block._type === "code" && "code" in block && typeof block.code === "string") {
32
+ return block.code;
33
+ }
34
+
35
+ // Image blocks - include alt text and caption
36
+ if (block._type === "image") {
37
+ const parts: string[] = [];
38
+ if ("alt" in block && typeof block.alt === "string" && block.alt) {
39
+ parts.push(block.alt);
40
+ }
41
+ if ("caption" in block && typeof block.caption === "string" && block.caption) {
42
+ parts.push(block.caption);
43
+ }
44
+ return parts.join(" ");
45
+ }
46
+
47
+ return "";
48
+ }
49
+
50
+ /**
51
+ * Extract plain text from Portable Text blocks
52
+ *
53
+ * Uses @portabletext/toolkit's toPlainText for standard blocks,
54
+ * plus extracts text from custom block types (code, images with alt/caption).
55
+ *
56
+ * @param blocks - Array of Portable Text blocks (or a JSON string)
57
+ * @returns Plain text content
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const text = extractPlainText([
62
+ * {
63
+ * _type: "block",
64
+ * _key: "abc",
65
+ * children: [{ _type: "span", _key: "s1", text: "Hello World" }]
66
+ * }
67
+ * ]);
68
+ * // Returns: "Hello World"
69
+ * ```
70
+ */
71
+ export function extractPlainText(blocks: PortableTextBlock[] | string | null | undefined): string {
72
+ if (!blocks) {
73
+ return "";
74
+ }
75
+
76
+ // Handle JSON string input
77
+ let parsedBlocks: PortableTextBlock[];
78
+ if (typeof blocks === "string") {
79
+ try {
80
+ parsedBlocks = JSON.parse(blocks);
81
+ } catch {
82
+ // If it's not valid JSON, treat as plain text
83
+ return blocks;
84
+ }
85
+ } else {
86
+ parsedBlocks = blocks;
87
+ }
88
+
89
+ if (!Array.isArray(parsedBlocks)) {
90
+ return "";
91
+ }
92
+
93
+ // Use official toPlainText for standard blocks.
94
+ // toPlainText expects `{ _type: string; [key: string]: any }[]` but our blocks use
95
+ // `unknown` index sigs. They're structurally compatible at runtime — spread each block
96
+ // to satisfy the wider index signature without an unsafe cast.
97
+ const toolkitBlocks = parsedBlocks.map((b) => {
98
+ const obj: Record<string, unknown> & { _type: string } = { _type: b._type };
99
+ for (const [key, val] of Object.entries(b)) {
100
+ obj[key] = val;
101
+ }
102
+ return obj;
103
+ });
104
+ const standardText = toPlainText(toolkitBlocks);
105
+
106
+ // Extract text from custom block types that toPlainText doesn't handle
107
+ const customTexts = parsedBlocks.map(extractCustomBlockText).filter((text) => text.length > 0);
108
+
109
+ // Combine both
110
+ const allTexts = [standardText, ...customTexts].filter((t) => t.length > 0);
111
+ return allTexts.join("\n");
112
+ }
113
+
114
+ /**
115
+ * Extract searchable text from a content entry
116
+ *
117
+ * Extracts text from specified fields, handling both plain text and Portable Text.
118
+ *
119
+ * @param entry - Content entry data
120
+ * @param fields - Field names to extract text from
121
+ * @returns Object mapping field names to extracted text
122
+ */
123
+ export function extractSearchableFields(
124
+ entry: Record<string, unknown>,
125
+ fields: string[],
126
+ ): Record<string, string> {
127
+ const result: Record<string, string> = {};
128
+
129
+ for (const field of fields) {
130
+ const value = entry[field];
131
+
132
+ if (value === null || value === undefined) {
133
+ result[field] = "";
134
+ continue;
135
+ }
136
+
137
+ if (typeof value === "string") {
138
+ // Could be plain text or JSON Portable Text
139
+ if (value.startsWith("[")) {
140
+ result[field] = extractPlainText(value);
141
+ } else {
142
+ result[field] = value;
143
+ }
144
+ } else if (Array.isArray(value)) {
145
+ // Validate the array looks like Portable Text before treating it as such
146
+ if (isPortableTextArray(value)) {
147
+ result[field] = extractPlainText(value);
148
+ } else {
149
+ result[field] = JSON.stringify(value);
150
+ }
151
+ } else if (typeof value === "object") {
152
+ // Object — serialize to JSON for searchable text
153
+ result[field] = JSON.stringify(value);
154
+ } else if (typeof value === "number" || typeof value === "boolean") {
155
+ result[field] = `${value}`;
156
+ } else {
157
+ result[field] = "";
158
+ }
159
+ }
160
+
161
+ return result;
162
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Search Types
3
+ *
4
+ * Type definitions for the EmDash search system.
5
+ */
6
+
7
+ /**
8
+ * Search configuration for a collection
9
+ */
10
+ export interface SearchConfig {
11
+ /** Whether search is enabled for this collection */
12
+ enabled: boolean;
13
+ /** Field weights for ranking (higher = more important) */
14
+ weights?: Record<string, number>;
15
+ }
16
+
17
+ /**
18
+ * Options for search queries
19
+ */
20
+ export interface SearchOptions {
21
+ /** Collections to search (defaults to all searchable collections) */
22
+ collections?: string[];
23
+ /** Filter by content status (defaults to 'published') */
24
+ status?: string;
25
+ /** Filter by locale (omit to search all locales) */
26
+ locale?: string;
27
+ /** Maximum results to return (defaults to 20) */
28
+ limit?: number;
29
+ /** Pagination cursor */
30
+ cursor?: string;
31
+ }
32
+
33
+ /**
34
+ * Options for collection-specific search
35
+ */
36
+ export interface CollectionSearchOptions {
37
+ /** Filter by content status (defaults to 'published') */
38
+ status?: string;
39
+ /** Filter by locale (omit to search all locales) */
40
+ locale?: string;
41
+ /** Maximum results to return (defaults to 20) */
42
+ limit?: number;
43
+ /** Pagination cursor */
44
+ cursor?: string;
45
+ }
46
+
47
+ /**
48
+ * A single search result
49
+ */
50
+ export interface SearchResult {
51
+ /** Collection the result belongs to */
52
+ collection: string;
53
+ /** Entry ID */
54
+ id: string;
55
+ /** Entry slug */
56
+ slug: string | null;
57
+ /** Content locale */
58
+ locale: string;
59
+ /** Entry title (if available) */
60
+ title?: string;
61
+ /** Highlighted snippet showing match context */
62
+ snippet?: string;
63
+ /** Relevance score (higher = more relevant) */
64
+ score: number;
65
+ }
66
+
67
+ /**
68
+ * Response from a search query
69
+ */
70
+ export interface SearchResponse {
71
+ /** Search results */
72
+ items: SearchResult[];
73
+ /** Cursor for next page of results */
74
+ nextCursor?: string;
75
+ }
76
+
77
+ /**
78
+ * Options for suggestion/autocomplete queries
79
+ */
80
+ export interface SuggestOptions {
81
+ /** Collections to search (defaults to all searchable collections) */
82
+ collections?: string[];
83
+ /** Filter by locale (omit to search all locales) */
84
+ locale?: string;
85
+ /** Maximum suggestions to return (defaults to 5) */
86
+ limit?: number;
87
+ }
88
+
89
+ /**
90
+ * A single suggestion result
91
+ */
92
+ export interface Suggestion {
93
+ /** Collection the suggestion belongs to */
94
+ collection: string;
95
+ /** Entry ID */
96
+ id: string;
97
+ /** Entry title */
98
+ title: string;
99
+ }
100
+
101
+ /**
102
+ * Search index statistics
103
+ */
104
+ export interface SearchStats {
105
+ collections: Record<
106
+ string,
107
+ {
108
+ /** Number of indexed entries */
109
+ indexed: number;
110
+ /** When the index was last rebuilt */
111
+ lastRebuilt?: string;
112
+ }
113
+ >;
114
+ }