emdash 0.7.0 → 0.9.0

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 (354) hide show
  1. package/dist/{adapters-Di31kZ28.d.mts → adapters-DoNJiveC.d.mts} +1 -1
  2. package/dist/{adapters-Di31kZ28.d.mts.map → adapters-DoNJiveC.d.mts.map} +1 -1
  3. package/dist/{apply-5uslYdUu.mjs → apply-BzltprvY.mjs} +90 -139
  4. package/dist/apply-BzltprvY.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.d.mts.map +1 -1
  7. package/dist/astro/index.mjs +194 -17
  8. package/dist/astro/index.mjs.map +1 -1
  9. package/dist/astro/middleware/auth.d.mts +6 -7
  10. package/dist/astro/middleware/auth.d.mts.map +1 -1
  11. package/dist/astro/middleware/auth.mjs +34 -57
  12. package/dist/astro/middleware/auth.mjs.map +1 -1
  13. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  14. package/dist/astro/middleware/redirect.mjs +17 -12
  15. package/dist/astro/middleware/redirect.mjs.map +1 -1
  16. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  17. package/dist/astro/middleware/request-context.mjs +9 -6
  18. package/dist/astro/middleware/request-context.mjs.map +1 -1
  19. package/dist/astro/middleware/setup.mjs +1 -1
  20. package/dist/astro/middleware.d.mts.map +1 -1
  21. package/dist/astro/middleware.mjs +301 -165
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/types.d.mts +34 -10
  24. package/dist/astro/types.d.mts.map +1 -1
  25. package/dist/{base64-MBPo9ozB.mjs → base64-BRICGH2l.mjs} +1 -1
  26. package/dist/{base64-MBPo9ozB.mjs.map → base64-BRICGH2l.mjs.map} +1 -1
  27. package/dist/{byline-C4OVd8b3.mjs → byline-BSaNL1w7.mjs} +5 -5
  28. package/dist/byline-BSaNL1w7.mjs.map +1 -0
  29. package/dist/bylines-CvJ3PYz2.mjs +113 -0
  30. package/dist/bylines-CvJ3PYz2.mjs.map +1 -0
  31. package/dist/cache-C6N_hhN7.mjs +65 -0
  32. package/dist/cache-C6N_hhN7.mjs.map +1 -0
  33. package/dist/{chunks-HGz06Soa.mjs → chunks-NBQVDOci.mjs} +8 -2
  34. package/dist/{chunks-HGz06Soa.mjs.map → chunks-NBQVDOci.mjs.map} +1 -1
  35. package/dist/cli/index.mjs +229 -31
  36. package/dist/cli/index.mjs.map +1 -1
  37. package/dist/client/cf-access.d.mts +1 -1
  38. package/dist/client/index.d.mts +1 -1
  39. package/dist/client/index.mjs +3 -3
  40. package/dist/client/index.mjs.map +1 -1
  41. package/dist/{config-BXwuX8Bx.mjs → config-BI0V3ICQ.mjs} +1 -1
  42. package/dist/{config-BXwuX8Bx.mjs.map → config-BI0V3ICQ.mjs.map} +1 -1
  43. package/dist/{content-D7J5y73J.mjs → content-8lOYF0pr.mjs} +43 -28
  44. package/dist/content-8lOYF0pr.mjs.map +1 -0
  45. package/dist/db/index.d.mts +3 -3
  46. package/dist/db/index.mjs +2 -2
  47. package/dist/db/libsql.d.mts +1 -1
  48. package/dist/db/libsql.d.mts.map +1 -1
  49. package/dist/db/libsql.mjs +7 -2
  50. package/dist/db/libsql.mjs.map +1 -1
  51. package/dist/db/postgres.d.mts +1 -1
  52. package/dist/db/sqlite.d.mts +1 -1
  53. package/dist/db/sqlite.d.mts.map +1 -1
  54. package/dist/db/sqlite.mjs +8 -3
  55. package/dist/db/sqlite.mjs.map +1 -1
  56. package/dist/{db-errors-D0UT85nC.mjs → db-errors-WRezodiz.mjs} +1 -1
  57. package/dist/{db-errors-D0UT85nC.mjs.map → db-errors-WRezodiz.mjs.map} +1 -1
  58. package/dist/{default-CME5YdZ3.mjs → default-D8ksjWhO.mjs} +1 -1
  59. package/dist/{default-CME5YdZ3.mjs.map → default-D8ksjWhO.mjs.map} +1 -1
  60. package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
  61. package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
  62. package/dist/{error-CiYn9yDu.mjs → error-D_-tqP-I.mjs} +1 -1
  63. package/dist/error-D_-tqP-I.mjs.map +1 -0
  64. package/dist/{index-De6_Xv3v.d.mts → index-BFRaVcD6.d.mts} +243 -40
  65. package/dist/index-BFRaVcD6.d.mts.map +1 -0
  66. package/dist/index.d.mts +11 -11
  67. package/dist/index.mjs +29 -25
  68. package/dist/{load-CBcmDIot.mjs → load-DDqMMvZL.mjs} +2 -2
  69. package/dist/{load-CBcmDIot.mjs.map → load-DDqMMvZL.mjs.map} +1 -1
  70. package/dist/{loader-DeiBJEMe.mjs → loader-CKLbBnhK.mjs} +32 -10
  71. package/dist/loader-CKLbBnhK.mjs.map +1 -0
  72. package/dist/{manifest-schema-V30qsMft.mjs → manifest-schema-DqWNC3lM.mjs} +45 -3
  73. package/dist/manifest-schema-DqWNC3lM.mjs.map +1 -0
  74. package/dist/media/index.d.mts +1 -1
  75. package/dist/media/index.mjs +1 -1
  76. package/dist/media/local-runtime.d.mts +7 -7
  77. package/dist/media/local-runtime.mjs +3 -3
  78. package/dist/{media-DqHVh136.mjs → media-BW32b4gi.mjs} +4 -7
  79. package/dist/media-BW32b4gi.mjs.map +1 -0
  80. package/dist/{mode-CpNnGkPz.mjs → mode-ier8jbBk.mjs} +1 -1
  81. package/dist/mode-ier8jbBk.mjs.map +1 -0
  82. package/dist/options-BVp3UsTS.mjs +117 -0
  83. package/dist/options-BVp3UsTS.mjs.map +1 -0
  84. package/dist/page/index.d.mts +2 -2
  85. package/dist/{placeholder-tzpqGWII.d.mts → placeholder-BE4o_2dc.d.mts} +1 -1
  86. package/dist/{placeholder-tzpqGWII.d.mts.map → placeholder-BE4o_2dc.d.mts.map} +1 -1
  87. package/dist/{placeholder-C-fk5hYI.mjs → placeholder-CIJejMlK.mjs} +1 -1
  88. package/dist/placeholder-CIJejMlK.mjs.map +1 -0
  89. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  90. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  91. package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
  92. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  93. package/dist/public-url-DByxYjUw.mjs +51 -0
  94. package/dist/public-url-DByxYjUw.mjs.map +1 -0
  95. package/dist/{query-g4Ug-9j9.mjs → query-Cg9ZKRQ0.mjs} +114 -16
  96. package/dist/query-Cg9ZKRQ0.mjs.map +1 -0
  97. package/dist/{redirect-CN0Rt9Ob.mjs → redirect-BhUBKRc1.mjs} +13 -8
  98. package/dist/redirect-BhUBKRc1.mjs.map +1 -0
  99. package/dist/{registry-Ci3WxVAr.mjs → registry-Dw70ChxB.mjs} +69 -11
  100. package/dist/registry-Dw70ChxB.mjs.map +1 -0
  101. package/dist/{request-cache-DiR961CV.mjs → request-cache-B-bmkipQ.mjs} +1 -1
  102. package/dist/request-cache-B-bmkipQ.mjs.map +1 -0
  103. package/dist/runner-Bnoj7vjK.d.mts +44 -0
  104. package/dist/runner-Bnoj7vjK.d.mts.map +1 -0
  105. package/dist/{runner-tQ7BJ4T7.mjs → runner-C7ADox5q.mjs} +185 -55
  106. package/dist/{runner-tQ7BJ4T7.mjs.map → runner-C7ADox5q.mjs.map} +1 -1
  107. package/dist/runtime.d.mts +6 -6
  108. package/dist/runtime.mjs +4 -4
  109. package/dist/{search-B0effn3j.mjs → search-dOGEccMa.mjs} +341 -152
  110. package/dist/search-dOGEccMa.mjs.map +1 -0
  111. package/dist/secrets-CW3reAnU.mjs +314 -0
  112. package/dist/secrets-CW3reAnU.mjs.map +1 -0
  113. package/dist/seed/index.d.mts +2 -2
  114. package/dist/seed/index.mjs +15 -14
  115. package/dist/seo/index.d.mts +1 -1
  116. package/dist/storage/local.d.mts +1 -1
  117. package/dist/storage/local.mjs +1 -1
  118. package/dist/storage/s3.d.mts +1 -1
  119. package/dist/storage/s3.d.mts.map +1 -1
  120. package/dist/storage/s3.mjs +4 -4
  121. package/dist/storage/s3.mjs.map +1 -1
  122. package/dist/{taxonomies-K2z0Uhnj.mjs → taxonomies-ZlRtD6AG.mjs} +14 -7
  123. package/dist/taxonomies-ZlRtD6AG.mjs.map +1 -0
  124. package/dist/{tokens-BFPFx3CA.mjs → tokens-D7zMmWi2.mjs} +2 -2
  125. package/dist/{tokens-BFPFx3CA.mjs.map → tokens-D7zMmWi2.mjs.map} +1 -1
  126. package/dist/{transport-BykRfpyy.mjs → transport-BeMCmin1.mjs} +6 -5
  127. package/dist/{transport-BykRfpyy.mjs.map → transport-BeMCmin1.mjs.map} +1 -1
  128. package/dist/{transport-H4Iwx7tC.d.mts → transport-DNEfeMaU.d.mts} +1 -1
  129. package/dist/{transport-H4Iwx7tC.d.mts.map → transport-DNEfeMaU.d.mts.map} +1 -1
  130. package/dist/types-4fVtCIm0.mjs +68 -0
  131. package/dist/types-4fVtCIm0.mjs.map +1 -0
  132. package/dist/{types-CnZYHyLW.d.mts → types-BSyXeCFW.d.mts} +24 -2
  133. package/dist/{types-CnZYHyLW.d.mts.map → types-BSyXeCFW.d.mts.map} +1 -1
  134. package/dist/{types-DgrIP0tF.d.mts → types-BuBIptGk.d.mts} +80 -106
  135. package/dist/types-BuBIptGk.d.mts.map +1 -0
  136. package/dist/{types-BH2L167P.mjs → types-CDbKp7ND.mjs} +1 -1
  137. package/dist/{types-BH2L167P.mjs.map → types-CDbKp7ND.mjs.map} +1 -1
  138. package/dist/{types-DDS4MxsT.mjs → types-CIOg5AR8.mjs} +1 -1
  139. package/dist/{types-DDS4MxsT.mjs.map → types-CIOg5AR8.mjs.map} +1 -1
  140. package/dist/{types-6CUZRrZP.d.mts → types-CJsYGpco.d.mts} +24 -2
  141. package/dist/{types-6CUZRrZP.d.mts.map → types-CJsYGpco.d.mts.map} +1 -1
  142. package/dist/types-CRxNbK-Z.mjs +68 -0
  143. package/dist/types-CRxNbK-Z.mjs.map +1 -0
  144. package/dist/{types-C2v0c34j.d.mts → types-CrtWgIvl.d.mts} +1 -1
  145. package/dist/{types-C2v0c34j.d.mts.map → types-CrtWgIvl.d.mts.map} +1 -1
  146. package/dist/{types-CFWjXmus.d.mts → types-M78DQ1lx.d.mts} +1 -1
  147. package/dist/{types-CFWjXmus.d.mts.map → types-M78DQ1lx.d.mts.map} +1 -1
  148. package/dist/{validate-CqsNItbt.mjs → validate-Baqf0slj.mjs} +3 -3
  149. package/dist/{validate-CqsNItbt.mjs.map → validate-Baqf0slj.mjs.map} +1 -1
  150. package/dist/{validate-kM8Pjuf7.d.mts → validate-BfQh_C_y.d.mts} +4 -4
  151. package/dist/{validate-kM8Pjuf7.d.mts.map → validate-BfQh_C_y.d.mts.map} +1 -1
  152. package/dist/validation-BfEI7tNe.mjs +144 -0
  153. package/dist/validation-BfEI7tNe.mjs.map +1 -0
  154. package/dist/version-DoxrVdYf.mjs +7 -0
  155. package/dist/{version-BnTKdfam.mjs.map → version-DoxrVdYf.mjs.map} +1 -1
  156. package/dist/zod-generator-CC0xNe_K.mjs +132 -0
  157. package/dist/zod-generator-CC0xNe_K.mjs.map +1 -0
  158. package/locals.d.ts +1 -6
  159. package/package.json +21 -7
  160. package/src/api/auth-storage.ts +37 -0
  161. package/src/api/error.ts +6 -0
  162. package/src/api/errors.ts +8 -0
  163. package/src/api/handlers/comments.ts +19 -4
  164. package/src/api/handlers/content.ts +151 -4
  165. package/src/api/handlers/device-flow.ts +5 -0
  166. package/src/api/handlers/index.ts +2 -0
  167. package/src/api/handlers/marketplace.ts +11 -4
  168. package/src/api/handlers/media.ts +8 -1
  169. package/src/api/handlers/menus.ts +160 -21
  170. package/src/api/handlers/oauth-authorization.ts +72 -33
  171. package/src/api/handlers/redirects.ts +16 -3
  172. package/src/api/handlers/revision.ts +23 -14
  173. package/src/api/handlers/sections.ts +8 -1
  174. package/src/api/handlers/taxonomies.ts +131 -22
  175. package/src/api/handlers/validation.ts +212 -0
  176. package/src/api/openapi/document.ts +4 -1
  177. package/src/api/public-url.ts +54 -5
  178. package/src/api/route-utils.ts +14 -0
  179. package/src/api/schemas/comments.ts +2 -2
  180. package/src/api/schemas/common.ts +1 -1
  181. package/src/api/schemas/content.ts +17 -0
  182. package/src/api/schemas/sections.ts +3 -3
  183. package/src/api/schemas/setup.ts +8 -0
  184. package/src/api/schemas/users.ts +1 -1
  185. package/src/api/schemas/widgets.ts +12 -10
  186. package/src/api/setup-complete.ts +40 -0
  187. package/src/api/types.ts +5 -1
  188. package/src/astro/integration/index.ts +30 -2
  189. package/src/astro/integration/routes.ts +28 -0
  190. package/src/astro/integration/runtime.ts +49 -1
  191. package/src/astro/integration/virtual-modules.ts +73 -2
  192. package/src/astro/integration/vite-config.ts +49 -13
  193. package/src/astro/middleware/auth.ts +34 -6
  194. package/src/astro/middleware/redirect.ts +29 -16
  195. package/src/astro/middleware/request-context.ts +15 -5
  196. package/src/astro/middleware.ts +41 -10
  197. package/src/astro/routes/PluginRegistry.tsx +10 -1
  198. package/src/astro/routes/api/auth/invite/complete.ts +6 -1
  199. package/src/astro/routes/api/auth/mode.ts +57 -0
  200. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +23 -3
  201. package/src/astro/routes/api/auth/oauth/[provider].ts +10 -4
  202. package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
  203. package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
  204. package/src/astro/routes/api/auth/signup/complete.ts +6 -1
  205. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
  206. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  207. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
  208. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
  209. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
  210. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
  211. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
  212. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
  213. package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
  214. package/src/astro/routes/api/content/[collection]/index.ts +1 -9
  215. package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
  216. package/src/astro/routes/api/import/wordpress/media.ts +2 -7
  217. package/src/astro/routes/api/import/wordpress/prepare.ts +9 -0
  218. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
  219. package/src/astro/routes/api/manifest.ts +62 -45
  220. package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
  221. package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
  222. package/src/astro/routes/api/openapi.json.ts +27 -10
  223. package/src/astro/routes/api/redirects/404s/index.ts +10 -4
  224. package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
  225. package/src/astro/routes/api/redirects/[id].ts +10 -4
  226. package/src/astro/routes/api/redirects/index.ts +7 -3
  227. package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
  228. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
  229. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
  230. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
  231. package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
  232. package/src/astro/routes/api/schema/collections/index.ts +1 -1
  233. package/src/astro/routes/api/search/index.ts +10 -2
  234. package/src/astro/routes/api/sections/[slug].ts +10 -4
  235. package/src/astro/routes/api/sections/index.ts +7 -3
  236. package/src/astro/routes/api/settings/email.ts +4 -9
  237. package/src/astro/routes/api/setup/admin-verify.ts +6 -1
  238. package/src/astro/routes/api/setup/admin.ts +8 -2
  239. package/src/astro/routes/api/setup/index.ts +2 -2
  240. package/src/astro/routes/api/setup/status.ts +3 -1
  241. package/src/astro/routes/api/snapshot.ts +44 -18
  242. package/src/astro/routes/api/taxonomies/index.ts +0 -1
  243. package/src/astro/routes/api/themes/preview.ts +11 -5
  244. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +4 -1
  245. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +4 -1
  246. package/src/astro/routes/api/widget-areas/[name].ts +4 -1
  247. package/src/astro/routes/api/widget-areas/index.ts +4 -1
  248. package/src/astro/types.ts +32 -3
  249. package/src/auth/allowed-origins.ts +168 -0
  250. package/src/auth/mode.ts +15 -3
  251. package/src/auth/passkey-config.ts +35 -13
  252. package/src/auth/providers/github-admin.tsx +29 -0
  253. package/src/auth/providers/github.ts +31 -0
  254. package/src/auth/providers/google-admin.tsx +44 -0
  255. package/src/auth/providers/google.ts +31 -0
  256. package/src/auth/types.ts +114 -4
  257. package/src/bylines/index.ts +37 -88
  258. package/src/cli/commands/auth.ts +28 -6
  259. package/src/cli/commands/bundle-utils.ts +11 -2
  260. package/src/cli/commands/bundle.ts +31 -9
  261. package/src/cli/commands/content.ts +13 -0
  262. package/src/cli/commands/login.ts +8 -1
  263. package/src/cli/commands/publish.ts +24 -0
  264. package/src/cli/commands/secrets.ts +183 -0
  265. package/src/cli/credentials.ts +1 -1
  266. package/src/cli/index.ts +5 -1
  267. package/src/client/index.ts +4 -4
  268. package/src/client/transport.ts +17 -7
  269. package/src/components/Break.astro +2 -2
  270. package/src/components/EmDashHead.astro +18 -13
  271. package/src/components/EmDashImage.astro +7 -6
  272. package/src/components/Embed.astro +1 -1
  273. package/src/components/Gallery.astro +6 -4
  274. package/src/components/Image.astro +9 -4
  275. package/src/components/InlinePortableTextEditor.tsx +106 -19
  276. package/src/components/LiveSearch.astro +5 -14
  277. package/src/config/secrets.ts +528 -0
  278. package/src/database/dialect-helpers.ts +50 -0
  279. package/src/database/migrations/034_published_at_index.ts +1 -1
  280. package/src/database/migrations/035_bounded_404_log.ts +56 -39
  281. package/src/database/migrations/runner.ts +156 -23
  282. package/src/database/repositories/audit.ts +6 -8
  283. package/src/database/repositories/byline.ts +6 -8
  284. package/src/database/repositories/comment.ts +12 -16
  285. package/src/database/repositories/content.ts +76 -52
  286. package/src/database/repositories/index.ts +1 -1
  287. package/src/database/repositories/media.ts +10 -13
  288. package/src/database/repositories/plugin-storage.ts +4 -6
  289. package/src/database/repositories/redirect.ts +26 -19
  290. package/src/database/repositories/taxonomy.ts +40 -3
  291. package/src/database/repositories/types.ts +57 -8
  292. package/src/database/repositories/user.ts +6 -8
  293. package/src/db/libsql.ts +1 -3
  294. package/src/db/sqlite.ts +2 -5
  295. package/src/emdash-runtime.ts +388 -247
  296. package/src/index.ts +14 -1
  297. package/src/loader.ts +30 -6
  298. package/src/mcp/server.ts +781 -141
  299. package/src/media/normalize.ts +1 -1
  300. package/src/media/url.ts +78 -0
  301. package/src/page/site-identity.ts +58 -0
  302. package/src/plugins/adapt-sandbox-entry.ts +22 -10
  303. package/src/plugins/context.ts +13 -10
  304. package/src/plugins/define-plugin.ts +40 -12
  305. package/src/plugins/email-console.ts +10 -3
  306. package/src/plugins/hooks.ts +34 -19
  307. package/src/plugins/index.ts +9 -0
  308. package/src/plugins/manifest-schema.ts +49 -2
  309. package/src/plugins/types.ts +174 -13
  310. package/src/preview/urls.ts +23 -3
  311. package/src/query.ts +149 -6
  312. package/src/redirects/cache.ts +38 -18
  313. package/src/request-cache.ts +3 -0
  314. package/src/schema/registry.ts +97 -5
  315. package/src/schema/zod-generator.ts +27 -5
  316. package/src/search/fts-manager.ts +0 -2
  317. package/src/search/query.ts +111 -26
  318. package/src/search/types.ts +8 -1
  319. package/src/sections/index.ts +7 -9
  320. package/src/seed/apply.ts +2 -0
  321. package/src/settings/index.ts +80 -6
  322. package/src/settings/types.ts +23 -1
  323. package/src/storage/s3.ts +12 -6
  324. package/src/taxonomies/index.ts +11 -1
  325. package/src/virtual-modules.d.ts +21 -1
  326. package/src/widgets/index.ts +1 -1
  327. package/dist/apply-5uslYdUu.mjs.map +0 -1
  328. package/dist/byline-C4OVd8b3.mjs.map +0 -1
  329. package/dist/bylines-hPTW79hw.mjs +0 -157
  330. package/dist/bylines-hPTW79hw.mjs.map +0 -1
  331. package/dist/cache-BkKBuIvS.mjs +0 -56
  332. package/dist/cache-BkKBuIvS.mjs.map +0 -1
  333. package/dist/chunk-ClPoSABd.mjs +0 -21
  334. package/dist/content-D7J5y73J.mjs.map +0 -1
  335. package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
  336. package/dist/error-CiYn9yDu.mjs.map +0 -1
  337. package/dist/index-De6_Xv3v.d.mts.map +0 -1
  338. package/dist/loader-DeiBJEMe.mjs.map +0 -1
  339. package/dist/manifest-schema-V30qsMft.mjs.map +0 -1
  340. package/dist/media-DqHVh136.mjs.map +0 -1
  341. package/dist/mode-CpNnGkPz.mjs.map +0 -1
  342. package/dist/placeholder-C-fk5hYI.mjs.map +0 -1
  343. package/dist/query-g4Ug-9j9.mjs.map +0 -1
  344. package/dist/redirect-CN0Rt9Ob.mjs.map +0 -1
  345. package/dist/registry-Ci3WxVAr.mjs.map +0 -1
  346. package/dist/request-cache-DiR961CV.mjs.map +0 -1
  347. package/dist/runner-BR2xKwhn.d.mts +0 -34
  348. package/dist/runner-BR2xKwhn.d.mts.map +0 -1
  349. package/dist/search-B0effn3j.mjs.map +0 -1
  350. package/dist/taxonomies-K2z0Uhnj.mjs.map +0 -1
  351. package/dist/types-CMMN0pNg.mjs +0 -31
  352. package/dist/types-CMMN0pNg.mjs.map +0 -1
  353. package/dist/types-DgrIP0tF.d.mts.map +0 -1
  354. package/dist/version-BnTKdfam.mjs +0 -7
@@ -226,14 +226,12 @@ export class PluginStorageRepository<T = unknown> implements StorageCollection<T
226
226
  query = query.where(({ eb }) => eb(sql.join(whereSqlParts, sql.raw("")), "=", sql.raw("1")));
227
227
  }
228
228
 
229
- // Handle cursor-based pagination
229
+ // Handle cursor-based pagination — throws on invalid cursor.
230
230
  if (cursor) {
231
231
  const decoded = decodeCursor(cursor);
232
- if (decoded) {
233
- query = query.where(({ eb }) =>
234
- eb(sql`(created_at, id)`, ">", sql`(${decoded.orderValue}, ${decoded.id})`),
235
- );
236
- }
232
+ query = query.where(({ eb }) =>
233
+ eb(sql`(created_at, id)`, ">", sql`(${decoded.orderValue}, ${decoded.id})`),
234
+ );
237
235
  }
238
236
 
239
237
  // Build ORDER BY using sql template
@@ -28,6 +28,9 @@ export const REFERRER_MAX_LENGTH = 512;
28
28
  /** Max stored length for the `User-Agent` header — truncated on insert. */
29
29
  export const USER_AGENT_MAX_LENGTH = 256;
30
30
 
31
+ /** Pattern to escape LIKE wildcards: %, _, and backslash */
32
+ const LIKE_ESCAPE_RE = /[\\%_]/g;
33
+
31
34
  /**
32
35
  * Truncate a header-derived string to `max` chars, preserving `null`/`undefined`
33
36
  * as `null`. Empty strings stay empty (the caller decides whether to coerce).
@@ -162,9 +165,15 @@ export class RedirectRepository {
162
165
  .limit(limit + 1);
163
166
 
164
167
  if (opts.search) {
165
- const term = `%${opts.search}%`;
168
+ // Escape LIKE wildcards in the search term to prevent injection.
169
+ // Must include ESCAPE clause for SQLite to recognize backslash as escape char.
170
+ const escaped = opts.search.replace(LIKE_ESCAPE_RE, (c) => `\\${c}`);
171
+ const term = `%${escaped}%`;
166
172
  query = query.where((eb) =>
167
- eb.or([eb("source", "like", term), eb("destination", "like", term)]),
173
+ eb.or([
174
+ sql<boolean>`source LIKE ${term} ESCAPE '\\'`,
175
+ sql<boolean>`destination LIKE ${term} ESCAPE '\\'`,
176
+ ]),
168
177
  );
169
178
  }
170
179
 
@@ -182,14 +191,12 @@ export class RedirectRepository {
182
191
 
183
192
  if (opts.cursor) {
184
193
  const decoded = decodeCursor(opts.cursor);
185
- if (decoded) {
186
- query = query.where((eb) =>
187
- eb.or([
188
- eb("created_at", "<", decoded.orderValue),
189
- eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)]),
190
- ]),
191
- );
192
- }
194
+ query = query.where((eb) =>
195
+ eb.or([
196
+ eb("created_at", "<", decoded.orderValue),
197
+ eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)]),
198
+ ]),
199
+ );
193
200
  }
194
201
 
195
202
  const rows = await query.execute();
@@ -504,19 +511,19 @@ export class RedirectRepository {
504
511
  .limit(limit + 1);
505
512
 
506
513
  if (opts.search) {
507
- query = query.where("path", "like", `%${opts.search}%`);
514
+ const escaped = opts.search.replace(LIKE_ESCAPE_RE, (c) => `\\${c}`);
515
+ const term = `%${escaped}%`;
516
+ query = query.where(sql<boolean>`path LIKE ${term} ESCAPE '\\'`);
508
517
  }
509
518
 
510
519
  if (opts.cursor) {
511
520
  const decoded = decodeCursor(opts.cursor);
512
- if (decoded) {
513
- query = query.where((eb) =>
514
- eb.or([
515
- eb("created_at", "<", decoded.orderValue),
516
- eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)]),
517
- ]),
518
- );
519
- }
521
+ query = query.where((eb) =>
522
+ eb.or([
523
+ eb("created_at", "<", decoded.orderValue),
524
+ eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)]),
525
+ ]),
526
+ );
520
527
  }
521
528
 
522
529
  const rows = await query.execute();
@@ -41,12 +41,15 @@ export class TaxonomyRepository {
41
41
  async create(input: CreateTaxonomyInput): Promise<Taxonomy> {
42
42
  const id = ulid();
43
43
 
44
+ // Empty-string parentId is coerced to null defensively. Higher layers
45
+ // also normalize this — see handleTermCreate / handleTermUpdate.
46
+ const parentId = input.parentId === undefined || input.parentId === "" ? null : input.parentId;
44
47
  const row: TaxonomyTable = {
45
48
  id,
46
49
  name: input.name,
47
50
  slug: input.slug,
48
51
  label: input.label,
49
- parent_id: input.parentId ?? null,
52
+ parent_id: parentId,
50
53
  data: input.data ? JSON.stringify(input.data) : null,
51
54
  };
52
55
 
@@ -90,11 +93,15 @@ export class TaxonomyRepository {
90
93
  * Get all terms for a taxonomy (e.g., all categories)
91
94
  */
92
95
  async findByName(name: string, options: { parentId?: string | null } = {}): Promise<Taxonomy[]> {
96
+ // `id asc` is a stable tiebreaker for terms that share a label.
97
+ // Without it the SQL ordering is implementation-defined when labels
98
+ // match, which breaks keyset pagination over `(label, id)`.
93
99
  let query = this.db
94
100
  .selectFrom("taxonomies")
95
101
  .selectAll()
96
102
  .where("name", "=", name)
97
- .orderBy("label", "asc");
103
+ .orderBy("label", "asc")
104
+ .orderBy("id", "asc");
98
105
 
99
106
  if (options.parentId !== undefined) {
100
107
  if (options.parentId === null) {
@@ -117,6 +124,7 @@ export class TaxonomyRepository {
117
124
  .selectAll()
118
125
  .where("parent_id", "=", parentId)
119
126
  .orderBy("label", "asc")
127
+ .orderBy("id", "asc")
120
128
  .execute();
121
129
 
122
130
  return rows.map((row) => this.rowToTaxonomy(row));
@@ -132,7 +140,10 @@ export class TaxonomyRepository {
132
140
  const updates: Partial<TaxonomyTable> = {};
133
141
  if (input.slug !== undefined) updates.slug = input.slug;
134
142
  if (input.label !== undefined) updates.label = input.label;
135
- if (input.parentId !== undefined) updates.parent_id = input.parentId;
143
+ if (input.parentId !== undefined) {
144
+ // Defense in depth: empty-string parentId means null (no parent).
145
+ updates.parent_id = input.parentId === "" ? null : input.parentId;
146
+ }
136
147
  if (input.data !== undefined) updates.data = JSON.stringify(input.data);
137
148
 
138
149
  if (Object.keys(updates).length > 0) {
@@ -278,6 +289,32 @@ export class TaxonomyRepository {
278
289
  return Number(result?.count || 0);
279
290
  }
280
291
 
292
+ /**
293
+ * Batch count entries for multiple taxonomy term IDs.
294
+ * Chunks the query at SQL_BATCH_SIZE to stay below D1's bind-parameter limit.
295
+ * Returns a Map from term ID to count.
296
+ */
297
+ async countEntriesForTerms(termIds: string[]): Promise<Map<string, number>> {
298
+ if (termIds.length === 0) return new Map();
299
+
300
+ const { chunks, SQL_BATCH_SIZE } = await import("../../utils/chunks.js");
301
+
302
+ const counts = new Map<string, number>();
303
+ for (const chunk of chunks(termIds, SQL_BATCH_SIZE)) {
304
+ const rows = await this.db
305
+ .selectFrom("content_taxonomies")
306
+ .select(["taxonomy_id", (eb) => eb.fn.count("entry_id").as("count")])
307
+ .where("taxonomy_id", "in", chunk)
308
+ .groupBy("taxonomy_id")
309
+ .execute();
310
+
311
+ for (const row of rows) {
312
+ counts.set(row.taxonomy_id, Number(row.count || 0));
313
+ }
314
+ }
315
+ return counts;
316
+ }
317
+
281
318
  /**
282
319
  * Convert database row to Taxonomy object
283
320
  */
@@ -1,5 +1,15 @@
1
1
  import { encodeBase64, decodeBase64 } from "../../utils/base64.js";
2
2
 
3
+ /**
4
+ * Hard cap on cursor length. Cursors we issue are short JSON-in-base64
5
+ * blobs; a real cursor is well under 200 chars. This guards against
6
+ * malicious callers passing megabyte-sized strings to force the base64
7
+ * decoder to allocate (decodeBase64 is O(N) in input size). The MCP and
8
+ * REST schemas also clamp at 2048 — this 4096 cap is a defense-in-depth
9
+ * floor inside the repository helpers.
10
+ */
11
+ const MAX_CURSOR_LENGTH = 4096;
12
+
3
13
  export interface CreateContentInput {
4
14
  type: string;
5
15
  slug?: string | null;
@@ -87,17 +97,45 @@ export function encodeCursor(orderValue: string, id: string): string {
87
97
  return encodeBase64(JSON.stringify({ orderValue, id }));
88
98
  }
89
99
 
90
- /** Decode a cursor to order value + id. Returns null if invalid. */
91
- export function decodeCursor(cursor: string): { orderValue: string; id: string } | null {
100
+ /**
101
+ * Thrown when a pagination cursor cannot be decoded.
102
+ *
103
+ * Repository callers should let this propagate; handler catch blocks
104
+ * map it to a structured `INVALID_CURSOR` error so client pagination
105
+ * bugs surface immediately rather than silently re-fetching the first
106
+ * page.
107
+ */
108
+ export class InvalidCursorError extends Error {
109
+ constructor(cursor: string) {
110
+ const display = cursor.length > 50 ? `${cursor.slice(0, 47)}...` : cursor;
111
+ super(`Invalid pagination cursor: ${display}`);
112
+ this.name = "InvalidCursorError";
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Decode a cursor to order value + id.
118
+ *
119
+ * Throws `InvalidCursorError` if the cursor is empty, not valid base64,
120
+ * not valid JSON, or doesn't contain string `orderValue` and `id` fields.
121
+ */
122
+ export function decodeCursor(cursor: string): { orderValue: string; id: string } {
123
+ if (!cursor) throw new InvalidCursorError(cursor);
124
+ if (cursor.length > MAX_CURSOR_LENGTH) throw new InvalidCursorError(cursor);
125
+ let parsed: unknown;
92
126
  try {
93
- const parsed = JSON.parse(decodeBase64(cursor));
94
- if (typeof parsed.orderValue === "string" && typeof parsed.id === "string") {
95
- return parsed;
96
- }
97
- return null;
127
+ parsed = JSON.parse(decodeBase64(cursor));
98
128
  } catch {
99
- return null;
129
+ throw new InvalidCursorError(cursor);
130
+ }
131
+ if (parsed === null || typeof parsed !== "object") {
132
+ throw new InvalidCursorError(cursor);
133
+ }
134
+ const candidate = parsed as { orderValue?: unknown; id?: unknown };
135
+ if (typeof candidate.orderValue !== "string" || typeof candidate.id !== "string") {
136
+ throw new InvalidCursorError(cursor);
100
137
  }
138
+ return { orderValue: candidate.orderValue, id: candidate.id };
101
139
  }
102
140
 
103
141
  export interface ContentItem {
@@ -121,6 +159,17 @@ export interface ContentItem {
121
159
  translationGroup: string | null;
122
160
  /** SEO metadata — only populated for collections with `has_seo` enabled */
123
161
  seo?: ContentSeo;
162
+ /**
163
+ * For collections that support `revisions`: when a draft revision exists,
164
+ * `data` reflects the unsaved draft and `liveData` carries the currently-
165
+ * published values. When no draft exists, `liveData` is undefined.
166
+ *
167
+ * Hydrated by `EmDashRuntime.hydrateDraftData()` — repositories themselves
168
+ * never set this field; it's purely a runtime-overlay concept that gives
169
+ * agents a clear picture of "draft vs. live" without re-fetching the
170
+ * revision history.
171
+ */
172
+ liveData?: Record<string, unknown>;
124
173
  }
125
174
 
126
175
  export class EmDashValidationError extends Error {
@@ -123,14 +123,12 @@ export class UserRepository {
123
123
 
124
124
  if (options.cursor) {
125
125
  const decoded = decodeCursor(options.cursor);
126
- if (decoded) {
127
- query = query.where((eb) =>
128
- eb.or([
129
- eb("created_at", "<", decoded.orderValue),
130
- eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)]),
131
- ]),
132
- );
133
- }
126
+ query = query.where((eb) =>
127
+ eb.or([
128
+ eb("created_at", "<", decoded.orderValue),
129
+ eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)]),
130
+ ]),
131
+ );
134
132
  }
135
133
 
136
134
  const rows = await query.execute();
package/src/db/libsql.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * Loaded at runtime via virtual module.
6
6
  */
7
7
 
8
+ import { LibsqlDialect } from "@libsql/kysely-libsql";
8
9
  import type { Dialect } from "kysely";
9
10
 
10
11
  import type { LibsqlConfig } from "./adapters.js";
@@ -13,9 +14,6 @@ import type { LibsqlConfig } from "./adapters.js";
13
14
  * Create a libSQL dialect from config
14
15
  */
15
16
  export function createDialect(config: LibsqlConfig): Dialect {
16
- // Dynamic import to avoid loading @libsql/kysely-libsql at config time
17
- const { LibsqlDialect } = require("@libsql/kysely-libsql");
18
-
19
17
  return new LibsqlDialect({
20
18
  url: config.url,
21
19
  authToken: config.authToken,
package/src/db/sqlite.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  * Loaded at runtime via virtual module.
6
6
  */
7
7
 
8
- import type { Dialect } from "kysely";
8
+ import BetterSqlite3 from "better-sqlite3";
9
+ import { type Dialect, SqliteDialect } from "kysely";
9
10
 
10
11
  import type { SqliteConfig } from "./adapters.js";
11
12
 
@@ -13,10 +14,6 @@ import type { SqliteConfig } from "./adapters.js";
13
14
  * Create a SQLite dialect from config
14
15
  */
15
16
  export function createDialect(config: SqliteConfig): Dialect {
16
- // Dynamic import to avoid loading better-sqlite3 at config time
17
- const BetterSqlite3 = require("better-sqlite3");
18
- const { SqliteDialect } = require("kysely");
19
-
20
17
  // Parse URL to get file path
21
18
  const url = config.url;
22
19
  const filePath = url.startsWith("file:") ? url.slice(5) : url;