emdash 0.7.0 → 0.8.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 (225) hide show
  1. package/dist/{adapters-Di31kZ28.d.mts → adapters-BKSf3T9R.d.mts} +1 -1
  2. package/dist/{adapters-Di31kZ28.d.mts.map → adapters-BKSf3T9R.d.mts.map} +1 -1
  3. package/dist/{apply-5uslYdUu.mjs → apply-x0eMK1lX.mjs} +18 -17
  4. package/dist/apply-x0eMK1lX.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 +86 -15
  8. package/dist/astro/index.mjs.map +1 -1
  9. package/dist/astro/middleware/auth.d.mts +5 -5
  10. package/dist/astro/middleware/auth.d.mts.map +1 -1
  11. package/dist/astro/middleware/auth.mjs +22 -2
  12. package/dist/astro/middleware/auth.mjs.map +1 -1
  13. package/dist/astro/middleware/redirect.mjs +2 -2
  14. package/dist/astro/middleware/request-context.mjs +1 -1
  15. package/dist/astro/middleware/setup.mjs +1 -1
  16. package/dist/astro/middleware.d.mts.map +1 -1
  17. package/dist/astro/middleware.mjs +259 -71
  18. package/dist/astro/middleware.mjs.map +1 -1
  19. package/dist/astro/types.d.mts +16 -8
  20. package/dist/astro/types.d.mts.map +1 -1
  21. package/dist/{byline-C4OVd8b3.mjs → byline-Chbr2GoP.mjs} +3 -3
  22. package/dist/byline-Chbr2GoP.mjs.map +1 -0
  23. package/dist/{bylines-hPTW79hw.mjs → bylines-CRNsVG88.mjs} +4 -4
  24. package/dist/{bylines-hPTW79hw.mjs.map → bylines-CRNsVG88.mjs.map} +1 -1
  25. package/dist/cli/index.mjs +16 -12
  26. package/dist/cli/index.mjs.map +1 -1
  27. package/dist/client/cf-access.d.mts +1 -1
  28. package/dist/client/index.d.mts +1 -1
  29. package/dist/client/index.mjs +1 -1
  30. package/dist/{content-D7J5y73J.mjs → content-BcQPYxdV.mjs} +13 -15
  31. package/dist/content-BcQPYxdV.mjs.map +1 -0
  32. package/dist/db/index.d.mts +3 -3
  33. package/dist/db/libsql.d.mts +1 -1
  34. package/dist/db/postgres.d.mts +1 -1
  35. package/dist/db/sqlite.d.mts +1 -1
  36. package/dist/{db-errors-D0UT85nC.mjs → db-errors-l1Qh2RPR.mjs} +1 -1
  37. package/dist/{db-errors-D0UT85nC.mjs.map → db-errors-l1Qh2RPR.mjs.map} +1 -1
  38. package/dist/{default-CME5YdZ3.mjs → default-DCVqE5ib.mjs} +1 -1
  39. package/dist/{default-CME5YdZ3.mjs.map → default-DCVqE5ib.mjs.map} +1 -1
  40. package/dist/{error-CiYn9yDu.mjs → error-zG5T1UGA.mjs} +1 -1
  41. package/dist/error-zG5T1UGA.mjs.map +1 -0
  42. package/dist/{index-De6_Xv3v.d.mts → index-DIb-CzNx.d.mts} +157 -14
  43. package/dist/index-DIb-CzNx.d.mts.map +1 -0
  44. package/dist/index.d.mts +11 -11
  45. package/dist/index.mjs +22 -20
  46. package/dist/{load-CBcmDIot.mjs → load-CyEoextb.mjs} +1 -1
  47. package/dist/{load-CBcmDIot.mjs.map → load-CyEoextb.mjs.map} +1 -1
  48. package/dist/{loader-DeiBJEMe.mjs → loader-CndGj8kM.mjs} +8 -6
  49. package/dist/loader-CndGj8kM.mjs.map +1 -0
  50. package/dist/{manifest-schema-V30qsMft.mjs → manifest-schema-DH9xhc6t.mjs} +13 -1
  51. package/dist/manifest-schema-DH9xhc6t.mjs.map +1 -0
  52. package/dist/media/index.d.mts +1 -1
  53. package/dist/media/local-runtime.d.mts +7 -7
  54. package/dist/media/local-runtime.mjs +2 -2
  55. package/dist/{media-DqHVh136.mjs → media-D8FbNsl0.mjs} +4 -7
  56. package/dist/media-D8FbNsl0.mjs.map +1 -0
  57. package/dist/{mode-CpNnGkPz.mjs → mode-BnAOqItE.mjs} +1 -1
  58. package/dist/mode-BnAOqItE.mjs.map +1 -0
  59. package/dist/page/index.d.mts +2 -2
  60. package/dist/placeholder-C-fk5hYI.mjs.map +1 -1
  61. package/dist/{placeholder-tzpqGWII.d.mts → placeholder-D29tWZ7o.d.mts} +1 -1
  62. package/dist/{placeholder-tzpqGWII.d.mts.map → placeholder-D29tWZ7o.d.mts.map} +1 -1
  63. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  64. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  65. package/dist/{query-g4Ug-9j9.mjs → query-fqEdLFms.mjs} +9 -9
  66. package/dist/{query-g4Ug-9j9.mjs.map → query-fqEdLFms.mjs.map} +1 -1
  67. package/dist/{redirect-CN0Rt9Ob.mjs → redirect-D_pshWdf.mjs} +4 -4
  68. package/dist/redirect-D_pshWdf.mjs.map +1 -0
  69. package/dist/{registry-Ci3WxVAr.mjs → registry-C3Mr0ODu.mjs} +33 -9
  70. package/dist/registry-C3Mr0ODu.mjs.map +1 -0
  71. package/dist/{request-cache-DiR961CV.mjs → request-cache-Ci7f5pBb.mjs} +1 -1
  72. package/dist/request-cache-Ci7f5pBb.mjs.map +1 -0
  73. package/dist/{runner-BR2xKwhn.d.mts → runner-OURCaApa.d.mts} +2 -2
  74. package/dist/{runner-BR2xKwhn.d.mts.map → runner-OURCaApa.d.mts.map} +1 -1
  75. package/dist/runtime.d.mts +6 -6
  76. package/dist/runtime.mjs +2 -2
  77. package/dist/{search-B0effn3j.mjs → search-BoZYFuUk.mjs} +227 -84
  78. package/dist/search-BoZYFuUk.mjs.map +1 -0
  79. package/dist/seed/index.d.mts +2 -2
  80. package/dist/seed/index.mjs +12 -12
  81. package/dist/seo/index.d.mts +1 -1
  82. package/dist/storage/local.d.mts +1 -1
  83. package/dist/storage/local.mjs +1 -1
  84. package/dist/storage/s3.d.mts +1 -1
  85. package/dist/storage/s3.d.mts.map +1 -1
  86. package/dist/storage/s3.mjs +4 -4
  87. package/dist/storage/s3.mjs.map +1 -1
  88. package/dist/{taxonomies-K2z0Uhnj.mjs → taxonomies-B4IAshV8.mjs} +5 -5
  89. package/dist/{taxonomies-K2z0Uhnj.mjs.map → taxonomies-B4IAshV8.mjs.map} +1 -1
  90. package/dist/{tokens-BFPFx3CA.mjs → tokens-D9vnZqYS.mjs} +1 -1
  91. package/dist/{tokens-BFPFx3CA.mjs.map → tokens-D9vnZqYS.mjs.map} +1 -1
  92. package/dist/{transport-BykRfpyy.mjs → transport-C9ugt2Nr.mjs} +1 -1
  93. package/dist/{transport-BykRfpyy.mjs.map → transport-C9ugt2Nr.mjs.map} +1 -1
  94. package/dist/{transport-H4Iwx7tC.d.mts → transport-CUnEL3Vs.d.mts} +1 -1
  95. package/dist/{transport-H4Iwx7tC.d.mts.map → transport-CUnEL3Vs.d.mts.map} +1 -1
  96. package/dist/types-BIgulNsW.mjs +68 -0
  97. package/dist/types-BIgulNsW.mjs.map +1 -0
  98. package/dist/{types-DDS4MxsT.mjs → types-Bm1dn-q3.mjs} +1 -1
  99. package/dist/{types-DDS4MxsT.mjs.map → types-Bm1dn-q3.mjs.map} +1 -1
  100. package/dist/{types-CnZYHyLW.d.mts → types-BmPPSUEx.d.mts} +1 -1
  101. package/dist/{types-CnZYHyLW.d.mts.map → types-BmPPSUEx.d.mts.map} +1 -1
  102. package/dist/{types-6CUZRrZP.d.mts → types-BrA0xf5I.d.mts} +24 -2
  103. package/dist/{types-6CUZRrZP.d.mts.map → types-BrA0xf5I.d.mts.map} +1 -1
  104. package/dist/{types-C2v0c34j.d.mts → types-CS8FIX7L.d.mts} +1 -1
  105. package/dist/{types-C2v0c34j.d.mts.map → types-CS8FIX7L.d.mts.map} +1 -1
  106. package/dist/{types-BH2L167P.mjs → types-CgqmmMJB.mjs} +1 -1
  107. package/dist/{types-BH2L167P.mjs.map → types-CgqmmMJB.mjs.map} +1 -1
  108. package/dist/{types-CFWjXmus.d.mts → types-DIMwPFub.d.mts} +1 -1
  109. package/dist/{types-CFWjXmus.d.mts.map → types-DIMwPFub.d.mts.map} +1 -1
  110. package/dist/{types-DgrIP0tF.d.mts → types-i36XcA_X.d.mts} +49 -6
  111. package/dist/types-i36XcA_X.d.mts.map +1 -0
  112. package/dist/{validate-CqsNItbt.mjs → validate-CxVsLehf.mjs} +2 -2
  113. package/dist/{validate-CqsNItbt.mjs.map → validate-CxVsLehf.mjs.map} +1 -1
  114. package/dist/{validate-kM8Pjuf7.d.mts → validate-DHxmpFJt.d.mts} +4 -4
  115. package/dist/{validate-kM8Pjuf7.d.mts.map → validate-DHxmpFJt.d.mts.map} +1 -1
  116. package/dist/validation-C-ZpN2GI.mjs +144 -0
  117. package/dist/validation-C-ZpN2GI.mjs.map +1 -0
  118. package/dist/version-Bbq8TCrz.mjs +7 -0
  119. package/dist/{version-BnTKdfam.mjs.map → version-Bbq8TCrz.mjs.map} +1 -1
  120. package/dist/zod-generator-CpwccCIv.mjs +132 -0
  121. package/dist/zod-generator-CpwccCIv.mjs.map +1 -0
  122. package/package.json +18 -5
  123. package/src/api/auth-storage.ts +37 -0
  124. package/src/api/error.ts +6 -0
  125. package/src/api/errors.ts +8 -0
  126. package/src/api/handlers/comments.ts +13 -0
  127. package/src/api/handlers/content.ts +122 -3
  128. package/src/api/handlers/index.ts +2 -0
  129. package/src/api/handlers/media.ts +8 -1
  130. package/src/api/handlers/menus.ts +160 -21
  131. package/src/api/handlers/redirects.ts +16 -3
  132. package/src/api/handlers/sections.ts +8 -1
  133. package/src/api/handlers/taxonomies.ts +128 -16
  134. package/src/api/handlers/validation.ts +212 -0
  135. package/src/api/openapi/document.ts +4 -1
  136. package/src/api/public-url.ts +6 -3
  137. package/src/api/route-utils.ts +14 -0
  138. package/src/api/schemas/common.ts +1 -1
  139. package/src/api/schemas/setup.ts +8 -0
  140. package/src/api/schemas/widgets.ts +12 -10
  141. package/src/api/setup-complete.ts +40 -0
  142. package/src/astro/integration/index.ts +13 -2
  143. package/src/astro/integration/routes.ts +28 -0
  144. package/src/astro/integration/runtime.ts +19 -1
  145. package/src/astro/integration/virtual-modules.ts +41 -0
  146. package/src/astro/integration/vite-config.ts +43 -12
  147. package/src/astro/middleware/auth.ts +21 -0
  148. package/src/astro/middleware.ts +18 -1
  149. package/src/astro/routes/PluginRegistry.tsx +10 -1
  150. package/src/astro/routes/api/auth/mode.ts +57 -0
  151. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +23 -3
  152. package/src/astro/routes/api/auth/oauth/[provider].ts +10 -4
  153. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +1 -1
  154. package/src/astro/routes/api/content/[collection]/index.ts +1 -9
  155. package/src/astro/routes/api/import/wordpress/media.ts +2 -7
  156. package/src/astro/routes/api/import/wordpress/prepare.ts +10 -0
  157. package/src/astro/routes/api/settings/email.ts +4 -9
  158. package/src/astro/routes/api/setup/admin.ts +8 -2
  159. package/src/astro/routes/api/setup/index.ts +2 -2
  160. package/src/astro/routes/api/setup/status.ts +3 -1
  161. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +4 -1
  162. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +4 -1
  163. package/src/astro/routes/api/widget-areas/[name].ts +4 -1
  164. package/src/astro/routes/api/widget-areas/index.ts +4 -1
  165. package/src/astro/types.ts +9 -0
  166. package/src/auth/mode.ts +15 -3
  167. package/src/auth/providers/github-admin.tsx +29 -0
  168. package/src/auth/providers/github.ts +31 -0
  169. package/src/auth/providers/google-admin.tsx +44 -0
  170. package/src/auth/providers/google.ts +31 -0
  171. package/src/auth/types.ts +114 -4
  172. package/src/cli/commands/bundle.ts +3 -1
  173. package/src/components/EmDashImage.astro +7 -6
  174. package/src/components/Gallery.astro +5 -3
  175. package/src/components/Image.astro +8 -3
  176. package/src/components/InlinePortableTextEditor.tsx +2 -1
  177. package/src/components/LiveSearch.astro +5 -14
  178. package/src/database/repositories/audit.ts +6 -8
  179. package/src/database/repositories/byline.ts +6 -8
  180. package/src/database/repositories/comment.ts +12 -16
  181. package/src/database/repositories/content.ts +40 -40
  182. package/src/database/repositories/index.ts +1 -1
  183. package/src/database/repositories/media.ts +10 -13
  184. package/src/database/repositories/plugin-storage.ts +4 -6
  185. package/src/database/repositories/redirect.ts +12 -16
  186. package/src/database/repositories/taxonomy.ts +14 -3
  187. package/src/database/repositories/types.ts +57 -8
  188. package/src/database/repositories/user.ts +6 -8
  189. package/src/emdash-runtime.ts +306 -90
  190. package/src/index.ts +5 -1
  191. package/src/loader.ts +6 -5
  192. package/src/mcp/server.ts +678 -105
  193. package/src/media/normalize.ts +1 -1
  194. package/src/media/url.ts +78 -0
  195. package/src/plugins/email-console.ts +10 -3
  196. package/src/plugins/hooks.ts +11 -0
  197. package/src/plugins/manifest-schema.ts +12 -0
  198. package/src/plugins/types.ts +23 -2
  199. package/src/query.ts +1 -1
  200. package/src/request-cache.ts +3 -0
  201. package/src/schema/registry.ts +41 -5
  202. package/src/search/fts-manager.ts +0 -2
  203. package/src/search/query.ts +111 -26
  204. package/src/search/types.ts +8 -1
  205. package/src/sections/index.ts +7 -9
  206. package/src/storage/s3.ts +12 -6
  207. package/src/virtual-modules.d.ts +21 -1
  208. package/src/widgets/index.ts +1 -1
  209. package/dist/apply-5uslYdUu.mjs.map +0 -1
  210. package/dist/byline-C4OVd8b3.mjs.map +0 -1
  211. package/dist/content-D7J5y73J.mjs.map +0 -1
  212. package/dist/error-CiYn9yDu.mjs.map +0 -1
  213. package/dist/index-De6_Xv3v.d.mts.map +0 -1
  214. package/dist/loader-DeiBJEMe.mjs.map +0 -1
  215. package/dist/manifest-schema-V30qsMft.mjs.map +0 -1
  216. package/dist/media-DqHVh136.mjs.map +0 -1
  217. package/dist/mode-CpNnGkPz.mjs.map +0 -1
  218. package/dist/redirect-CN0Rt9Ob.mjs.map +0 -1
  219. package/dist/registry-Ci3WxVAr.mjs.map +0 -1
  220. package/dist/request-cache-DiR961CV.mjs.map +0 -1
  221. package/dist/search-B0effn3j.mjs.map +0 -1
  222. package/dist/types-CMMN0pNg.mjs +0 -31
  223. package/dist/types-CMMN0pNg.mjs.map +0 -1
  224. package/dist/types-DgrIP0tF.d.mts.map +0 -1
  225. package/dist/version-BnTKdfam.mjs +0 -7
@@ -1,21 +1,23 @@
1
1
  import { n as validateJsonFieldName, r as validatePluginIdentifier, t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
2
2
  import { o as jsonExtractExpr } from "./dialect-helpers-DhTzaUxP.mjs";
3
- import { a as slugify, r as RevisionRepository, t as ContentRepository } from "./content-D7J5y73J.mjs";
3
+ import { a as slugify, r as RevisionRepository, t as ContentRepository } from "./content-BcQPYxdV.mjs";
4
4
  import { r as encodeBase64, t as decodeBase64 } from "./base64-MBPo9ozB.mjs";
5
- import { n as decodeCursor, r as encodeCursor, t as EmDashValidationError } from "./types-CMMN0pNg.mjs";
6
- import { t as MediaRepository } from "./media-DqHVh136.mjs";
7
- import { a as ssrfSafeFetch, i as resolveAndValidateExternalUrl, o as stripCredentialHeaders, p as OptionsRepository, r as SsrfError, s as validateExternalUrl } from "./apply-5uslYdUu.mjs";
5
+ import { i as encodeCursor, n as InvalidCursorError, r as decodeCursor, t as EmDashValidationError } from "./types-BIgulNsW.mjs";
6
+ import { t as MediaRepository } from "./media-D8FbNsl0.mjs";
7
+ import { a as ssrfSafeFetch, i as resolveAndValidateExternalUrl, o as stripCredentialHeaders, p as OptionsRepository, r as SsrfError, s as validateExternalUrl } from "./apply-x0eMK1lX.mjs";
8
8
  import { t as withTransaction } from "./transaction-Cn2rjY78.mjs";
9
- import { t as RedirectRepository } from "./redirect-CN0Rt9Ob.mjs";
9
+ import { t as RedirectRepository } from "./redirect-D_pshWdf.mjs";
10
10
  import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-HGz06Soa.mjs";
11
- import { t as BylineRepository } from "./byline-C4OVd8b3.mjs";
11
+ import { t as BylineRepository } from "./byline-Chbr2GoP.mjs";
12
12
  import { r as isI18nEnabled } from "./config-BXwuX8Bx.mjs";
13
13
  import { r as invalidateRedirectCache } from "./cache-BkKBuIvS.mjs";
14
- import { i as FTSManager, n as SchemaRegistry } from "./registry-Ci3WxVAr.mjs";
15
- import { n as getDb } from "./loader-DeiBJEMe.mjs";
16
- import { n as requestCached } from "./request-cache-DiR961CV.mjs";
17
- import { i as pluginManifestSchema } from "./manifest-schema-V30qsMft.mjs";
18
- import { t as generatePreviewToken } from "./tokens-BFPFx3CA.mjs";
14
+ import { t as isMissingTableError } from "./db-errors-l1Qh2RPR.mjs";
15
+ import { r as hashString } from "./zod-generator-CpwccCIv.mjs";
16
+ import { i as FTSManager, n as SchemaRegistry } from "./registry-C3Mr0ODu.mjs";
17
+ import { n as getDb } from "./loader-CndGj8kM.mjs";
18
+ import { n as requestCached } from "./request-cache-Ci7f5pBb.mjs";
19
+ import { i as pluginManifestSchema } from "./manifest-schema-DH9xhc6t.mjs";
20
+ import { t as generatePreviewToken } from "./tokens-D9vnZqYS.mjs";
19
21
  import { sql } from "kysely";
20
22
  import { AsyncLocalStorage } from "node:async_hooks";
21
23
  import { ulid } from "ulidx";
@@ -78,7 +80,7 @@ var UserRepository = class UserRepository {
78
80
  if (options.role !== void 0) query = query.where("role", "=", UserRepository.resolveRole(options.role));
79
81
  if (options.cursor) {
80
82
  const decoded = decodeCursor(options.cursor);
81
- if (decoded) query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
83
+ query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
82
84
  }
83
85
  const rows = await query.execute();
84
86
  const items = rows.slice(0, limit).map((row) => this.rowToUser(row));
@@ -228,7 +230,7 @@ var CommentRepository = class CommentRepository {
228
230
  if (options.status) query = query.where("status", "=", options.status);
229
231
  if (options.cursor) {
230
232
  const decoded = decodeCursor(options.cursor);
231
- if (decoded) query = query.where((eb) => eb.or([eb("created_at", ">", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", ">", decoded.id)])]));
233
+ query = query.where((eb) => eb.or([eb("created_at", ">", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", ">", decoded.id)])]));
232
234
  }
233
235
  query = query.orderBy("created_at", "asc").orderBy("id", "asc").limit(limit + 1);
234
236
  const rows = await query.execute();
@@ -259,7 +261,7 @@ var CommentRepository = class CommentRepository {
259
261
  }
260
262
  if (options.cursor) {
261
263
  const decoded = decodeCursor(options.cursor);
262
- if (decoded) query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
264
+ query = query.where((eb) => eb.or([eb("created_at", "<", decoded.orderValue), eb.and([eb("created_at", "=", decoded.orderValue), eb("id", "<", decoded.id)])]));
263
265
  }
264
266
  query = query.orderBy("created_at", "desc").orderBy("id", "desc").limit(limit + 1);
265
267
  const rows = await query.execute();
@@ -683,7 +685,7 @@ var PluginStorageRepository = class {
683
685
  }
684
686
  if (cursor) {
685
687
  const decoded = decodeCursor(cursor);
686
- if (decoded) query = query.where(({ eb }) => eb(sql`(created_at, id)`, ">", sql`(${decoded.orderValue}, ${decoded.id})`));
688
+ query = query.where(({ eb }) => eb(sql`(created_at, id)`, ">", sql`(${decoded.orderValue}, ${decoded.id})`));
687
689
  }
688
690
  if (Object.keys(orderBy).length > 0) for (const [field, direction] of Object.entries(orderBy)) {
689
691
  const extract = jsonExtract(this.db, field);
@@ -976,6 +978,16 @@ function validateRev(rev, item) {
976
978
  //#endregion
977
979
  //#region src/api/handlers/content.ts
978
980
  /**
981
+ * Narrow a caught error to one carrying a structured `apiError` discriminant.
982
+ * Used by transaction callbacks that want to surface a specific error code
983
+ * through the standard Error throwing path.
984
+ */
985
+ function hasApiError(error) {
986
+ if (!(error instanceof Error) || !("apiError" in error)) return false;
987
+ const { apiError } = error;
988
+ return typeof apiError === "object" && apiError !== null && "code" in apiError && typeof apiError.code === "string";
989
+ }
990
+ /**
979
991
  * Extract a slug source (title or name) from content data.
980
992
  * Returns null if no suitable string field is found.
981
993
  */
@@ -1125,6 +1137,27 @@ async function handleContentList(db, collection, params) {
1125
1137
  }
1126
1138
  };
1127
1139
  } catch (error) {
1140
+ if (error instanceof InvalidCursorError) return {
1141
+ success: false,
1142
+ error: {
1143
+ code: "INVALID_CURSOR",
1144
+ message: error.message
1145
+ }
1146
+ };
1147
+ if (isMissingTableError(error)) return {
1148
+ success: false,
1149
+ error: {
1150
+ code: "COLLECTION_NOT_FOUND",
1151
+ message: `Collection '${collection}' not found`
1152
+ }
1153
+ };
1154
+ if (error instanceof EmDashValidationError) return {
1155
+ success: false,
1156
+ error: {
1157
+ code: "VALIDATION_ERROR",
1158
+ message: error.message
1159
+ }
1160
+ };
1128
1161
  console.error("Content list error:", error);
1129
1162
  return {
1130
1163
  success: false,
@@ -1255,6 +1288,37 @@ async function handleContentCreate(db, collection, body) {
1255
1288
  }
1256
1289
  };
1257
1290
  } catch (error) {
1291
+ if (isMissingTableError(error)) return {
1292
+ success: false,
1293
+ error: {
1294
+ code: "COLLECTION_NOT_FOUND",
1295
+ message: `Collection '${collection}' not found`
1296
+ }
1297
+ };
1298
+ if (error instanceof EmDashValidationError) return {
1299
+ success: false,
1300
+ error: {
1301
+ code: "VALIDATION_ERROR",
1302
+ message: error.message
1303
+ }
1304
+ };
1305
+ const message = error instanceof Error ? error.message.toLowerCase() : "";
1306
+ if (message.includes("unique constraint failed") || message.includes("duplicate key")) {
1307
+ if (message.includes("slug")) return {
1308
+ success: false,
1309
+ error: {
1310
+ code: "SLUG_CONFLICT",
1311
+ message: `Slug '${body.slug ?? "(auto-generated)"}' already exists in collection '${collection}'`
1312
+ }
1313
+ };
1314
+ return {
1315
+ success: false,
1316
+ error: {
1317
+ code: "CONFLICT",
1318
+ message: "Unique constraint violation"
1319
+ }
1320
+ };
1321
+ }
1258
1322
  console.error("Content create error:", error);
1259
1323
  return {
1260
1324
  success: false,
@@ -1324,13 +1388,41 @@ async function handleContentUpdate(db, collection, id, body) {
1324
1388
  }
1325
1389
  };
1326
1390
  } catch (error) {
1327
- if (error instanceof Error && "apiError" in error) {
1328
- const { code } = error.apiError;
1391
+ if (hasApiError(error)) return {
1392
+ success: false,
1393
+ error: {
1394
+ code: error.apiError.code,
1395
+ message: error.message
1396
+ }
1397
+ };
1398
+ if (isMissingTableError(error)) return {
1399
+ success: false,
1400
+ error: {
1401
+ code: "COLLECTION_NOT_FOUND",
1402
+ message: `Collection '${collection}' not found`
1403
+ }
1404
+ };
1405
+ if (error instanceof EmDashValidationError) return {
1406
+ success: false,
1407
+ error: {
1408
+ code: "VALIDATION_ERROR",
1409
+ message: error.message
1410
+ }
1411
+ };
1412
+ const message = error instanceof Error ? error.message.toLowerCase() : "";
1413
+ if (message.includes("unique constraint failed") || message.includes("duplicate key")) {
1414
+ if (message.includes("slug")) return {
1415
+ success: false,
1416
+ error: {
1417
+ code: "SLUG_CONFLICT",
1418
+ message: `Slug '${body.slug ?? id}' already exists in collection '${collection}'`
1419
+ }
1420
+ };
1329
1421
  return {
1330
1422
  success: false,
1331
1423
  error: {
1332
- code,
1333
- message: error.message
1424
+ code: "CONFLICT",
1425
+ message: "Unique constraint violation"
1334
1426
  }
1335
1427
  };
1336
1428
  }
@@ -1519,6 +1611,13 @@ async function handleContentListTrashed(db, collection, options = {}) {
1519
1611
  }
1520
1612
  };
1521
1613
  } catch (error) {
1614
+ if (error instanceof InvalidCursorError) return {
1615
+ success: false,
1616
+ error: {
1617
+ code: "INVALID_CURSOR",
1618
+ message: error.message
1619
+ }
1620
+ };
1522
1621
  console.error("Content list trashed error:", error);
1523
1622
  return {
1524
1623
  success: false,
@@ -1841,37 +1940,6 @@ async function syncNonTranslatableFields(trx, collectionSlug, updatedItemId, tra
1841
1940
  `.execute(trx);
1842
1941
  }
1843
1942
 
1844
- //#endregion
1845
- //#region src/utils/hash.ts
1846
- /**
1847
- * SHA-256 hash of a string, truncated to 16 hex chars (64 bits).
1848
- * For cache invalidation / ETags — not for security.
1849
- */
1850
- async function hashString(content) {
1851
- const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(content));
1852
- return Array.from(new Uint8Array(buf).slice(0, 8), (b) => b.toString(16).padStart(2, "0")).join("");
1853
- }
1854
- /**
1855
- * Compute content hash using Web Crypto API
1856
- *
1857
- * Uses SHA-1 which is the fastest option in SubtleCrypto.
1858
- * SHA-1 is cryptographically weak but fine for content deduplication
1859
- * where we only need to detect identical files, not resist attacks.
1860
- *
1861
- * Returns hex string prefixed with "sha1:" for future-proofing
1862
- */
1863
- async function computeContentHash(content) {
1864
- let buf;
1865
- if (content instanceof ArrayBuffer) buf = content;
1866
- else {
1867
- buf = new ArrayBuffer(content.byteLength);
1868
- new Uint8Array(buf).set(content);
1869
- }
1870
- const hashBuffer = await crypto.subtle.digest("SHA-1", buf);
1871
- const hashArray = new Uint8Array(hashBuffer);
1872
- return `sha1:${Array.from(hashArray, (b) => b.toString(16).padStart(2, "0")).join("")}`;
1873
- }
1874
-
1875
1943
  //#endregion
1876
1944
  //#region src/api/handlers/manifest.ts
1877
1945
  /**
@@ -2106,7 +2174,14 @@ async function handleMediaList(db, params) {
2106
2174
  nextCursor: result.nextCursor
2107
2175
  }
2108
2176
  };
2109
- } catch {
2177
+ } catch (error) {
2178
+ if (error instanceof InvalidCursorError) return {
2179
+ success: false,
2180
+ error: {
2181
+ code: "INVALID_CURSOR",
2182
+ message: error.message
2183
+ }
2184
+ };
2110
2185
  return {
2111
2186
  success: false,
2112
2187
  error: {
@@ -2436,7 +2511,7 @@ async function getSectionsWithDb(db, options = {}) {
2436
2511
  query = query.orderBy("title", "asc").orderBy("id", "asc");
2437
2512
  if (options.cursor) {
2438
2513
  const decoded = decodeCursor(options.cursor);
2439
- if (decoded) query = query.where((eb) => eb.or([eb("title", ">", decoded.orderValue), eb.and([eb("title", "=", decoded.orderValue), eb("id", ">", decoded.id)])]));
2514
+ query = query.where((eb) => eb.or([eb("title", ">", decoded.orderValue), eb.and([eb("title", "=", decoded.orderValue), eb("id", ">", decoded.id)])]));
2440
2515
  }
2441
2516
  query = query.limit(limit + 1);
2442
2517
  const rows = await query.$castTo().execute();
@@ -2542,7 +2617,7 @@ const VALID_ROLE_LEVELS = new Set([
2542
2617
  const roleLevel = z$1.coerce.number().int().refine((n) => VALID_ROLE_LEVELS.has(n), { message: "Invalid role level. Must be 10, 20, 30, 40, or 50" });
2543
2618
  /** Pagination query params — cursor-based */
2544
2619
  const cursorPaginationQuery = z$1.object({
2545
- cursor: z$1.string().optional().meta({ description: "Opaque cursor for pagination" }),
2620
+ cursor: z$1.string().max(2048).optional().meta({ description: "Opaque cursor for pagination" }),
2546
2621
  limit: z$1.coerce.number().int().min(1).max(100).optional().default(50).meta({ description: "Maximum number of items to return (1-100, default 50)" })
2547
2622
  }).meta({ id: "CursorPaginationQuery" });
2548
2623
  /** Pagination query params — offset-based */
@@ -3498,6 +3573,8 @@ const setupAdminBody = z$1.object({
3498
3573
  name: z$1.string().optional()
3499
3574
  });
3500
3575
  const setupAdminVerifyBody = z$1.object({ credential: registrationCredential });
3576
+ const atprotoLoginBody = z$1.object({ handle: z$1.string().trim().min(1) });
3577
+ const setupAtprotoAdminBody = z$1.object({ handle: z$1.string().trim().min(1) });
3501
3578
 
3502
3579
  //#endregion
3503
3580
  //#region src/api/schemas/users.ts
@@ -3601,18 +3678,15 @@ const widgetAreaSchema = z$1.object({
3601
3678
  }).meta({ id: "WidgetArea" });
3602
3679
  const widgetSchema = z$1.object({
3603
3680
  id: z$1.string(),
3604
- area_id: z$1.string(),
3605
- type: z$1.string(),
3606
- title: z$1.string().nullable(),
3607
- content: z$1.string().nullable(),
3608
- menu_name: z$1.string().nullable(),
3609
- component_id: z$1.string().nullable(),
3610
- component_props: z$1.string().nullable(),
3611
- sort_order: z$1.number().int(),
3612
- created_at: z$1.string(),
3613
- updated_at: z$1.string()
3681
+ type: widgetType,
3682
+ title: z$1.string().optional(),
3683
+ content: z$1.array(z$1.record(z$1.string(), z$1.unknown())).optional(),
3684
+ menuName: z$1.string().optional(),
3685
+ componentId: z$1.string().optional(),
3686
+ componentProps: z$1.record(z$1.string(), z$1.unknown()).optional()
3614
3687
  }).meta({ id: "Widget" });
3615
3688
  const widgetAreaWithWidgetsSchema = widgetAreaSchema.extend({ widgets: z$1.array(widgetSchema) }).meta({ id: "WidgetAreaWithWidgets" });
3689
+ const widgetAreaWithWidgetsAndCountSchema = widgetAreaWithWidgetsSchema.extend({ widgetCount: z$1.number().int() }).meta({ id: "WidgetAreaWithWidgetsAndCount" });
3616
3690
 
3617
3691
  //#endregion
3618
3692
  //#region src/api/schemas/redirects.ts
@@ -6815,6 +6889,15 @@ var HookPipeline = class HookPipeline {
6815
6889
  return (this.hooks.get(hookName) ?? []).filter((h) => h.exclusive).map((h) => ({ pluginId: h.pluginId }));
6816
6890
  }
6817
6891
  /**
6892
+ * Get all plugins that registered a non-exclusive handler for a given
6893
+ * hook (e.g. `email:beforeSend`, `email:afterSend`), preserving priority
6894
+ * order. Partitions with `getExclusiveHookProviders()`, which returns
6895
+ * plugins whose registration is marked `exclusive: true`.
6896
+ */
6897
+ getHookProviders(hookName) {
6898
+ return (this.hooks.get(hookName) ?? []).filter((h) => !h.exclusive).map((h) => ({ pluginId: h.pluginId }));
6899
+ }
6900
+ /**
6818
6901
  * Invoke an exclusive hook — dispatch only to the selected provider.
6819
6902
  * Returns null if no provider is selected or if the selected hook
6820
6903
  * is not found in the pipeline.
@@ -7058,8 +7141,15 @@ const MAX_STORED_EMAILS = 100;
7058
7141
  * instances (the runtime and the route handler may load separate copies
7059
7142
  * of this module, but globalThis is always the same object).
7060
7143
  */
7061
- const GLOBAL_KEY = "__emdash_dev_emails__";
7062
- const storedEmails = globalThis[GLOBAL_KEY] ??= [];
7144
+ const GLOBAL_KEY = Symbol.for("emdash:dev-emails");
7145
+ const g = globalThis;
7146
+ const storedEmails = (() => {
7147
+ const existing = g[GLOBAL_KEY];
7148
+ if (existing) return existing;
7149
+ const fresh = [];
7150
+ g[GLOBAL_KEY] = fresh;
7151
+ return fresh;
7152
+ })();
7063
7153
  /**
7064
7154
  * The email:deliver handler for the dev console provider.
7065
7155
  * Logs to console and stores in memory.
@@ -9186,6 +9276,22 @@ const WHITESPACE_SPLIT_PATTERN = /\s+/;
9186
9276
  const FTS_OPERATORS_PATTERN = /\b(AND|OR|NOT|NEAR)\b/i;
9187
9277
  const DOUBLE_QUOTE_PATTERN = /"/g;
9188
9278
  /**
9279
+ * Detect FTS5 query syntax errors. Match specifically on the SQLite FTS5
9280
+ * error fingerprints rather than a broad "fts5" / "syntax error" filter
9281
+ * (which would also swallow internal table-corruption errors). The two
9282
+ * fingerprints we care about are:
9283
+ *
9284
+ * - "fts5: syntax error near …" — unbalanced quotes, stray operators,
9285
+ * other malformed user input
9286
+ * - "unknown special query: …" — bare special tokens like `^*` that
9287
+ * parse but don't resolve to a real FTS5 directive
9288
+ */
9289
+ function isFts5SyntaxError(error) {
9290
+ if (!(error instanceof Error)) return false;
9291
+ const message = error.message.toLowerCase();
9292
+ return message.includes("fts5: syntax error") || message.includes("unknown special query");
9293
+ }
9294
+ /**
9189
9295
  * Search across multiple collections
9190
9296
  *
9191
9297
  * Public API that auto-injects the database.
@@ -9282,7 +9388,9 @@ async function searchSingleCollection(db, collection, query, options, weights) {
9282
9388
  bm25Args = weightValues.join(", ");
9283
9389
  }
9284
9390
  const bm25Expr = bm25Args ? `bm25("${ftsTable}", ${bm25Args})` : `bm25("${ftsTable}")`;
9285
- return (await sql`
9391
+ let results;
9392
+ try {
9393
+ results = await sql`
9286
9394
  SELECT
9287
9395
  c.id,
9288
9396
  c.slug,
@@ -9298,16 +9406,45 @@ async function searchSingleCollection(db, collection, query, options, weights) {
9298
9406
  ${locale ? sql`AND c.locale = ${locale}` : sql``}
9299
9407
  ORDER BY score
9300
9408
  LIMIT ${limit}
9301
- `.execute(db)).rows.map((row) => ({
9409
+ `.execute(db);
9410
+ } catch (error) {
9411
+ if (isFts5SyntaxError(error)) return [];
9412
+ throw error;
9413
+ }
9414
+ return results.rows.map((row) => ({
9302
9415
  collection,
9303
9416
  id: row.id,
9304
9417
  slug: row.slug,
9305
9418
  locale: row.locale,
9306
9419
  title: row.title ?? void 0,
9307
- snippet: row.snippet,
9420
+ snippet: row.snippet === null ? void 0 : sanitizeSnippet(row.snippet),
9308
9421
  score: Math.abs(row.score)
9309
9422
  }));
9310
9423
  }
9424
+ const SNIPPET_AMP_RE = /&/g;
9425
+ const SNIPPET_LT_RE = /</g;
9426
+ const SNIPPET_GT_RE = />/g;
9427
+ const SNIPPET_QUOT_RE = /"/g;
9428
+ const SNIPPET_APOS_RE = /'/g;
9429
+ /**
9430
+ * Make an FTS5 snippet safe to render with `set:html` / `innerHTML`.
9431
+ *
9432
+ * SQLite's `snippet()` function splices literal `<mark>` and `</mark>`
9433
+ * markers around matched terms but does not escape the surrounding
9434
+ * source text. Posts that legitimately contain `<`, `>`, `&`, `"` or
9435
+ * `'` would render as broken markup, and a `<script>` literal in a
9436
+ * title (or any other indexed field) would execute when displayed.
9437
+ *
9438
+ * The fix: HTML-escape the whole string, which turns the markers into
9439
+ * `&lt;mark&gt;` / `&lt;/mark&gt;`. Then restore those two patterns to
9440
+ * their original tag form. The result is "the indexed text with all
9441
+ * HTML metacharacters escaped, plus a small set of literal `<mark>`
9442
+ * highlight tags around matched terms" — which matches the API's
9443
+ * documented contract.
9444
+ */
9445
+ function sanitizeSnippet(snippet) {
9446
+ return snippet.replace(SNIPPET_AMP_RE, "&amp;").replace(SNIPPET_LT_RE, "&lt;").replace(SNIPPET_GT_RE, "&gt;").replace(SNIPPET_QUOT_RE, "&quot;").replace(SNIPPET_APOS_RE, "&#39;").replaceAll("&lt;mark&gt;", "<mark>").replaceAll("&lt;/mark&gt;", "</mark>");
9447
+ }
9311
9448
  /**
9312
9449
  * Get search suggestions for autocomplete
9313
9450
  *
@@ -9331,20 +9468,26 @@ async function getSuggestions(db, query, options = {}) {
9331
9468
  const contentTable = ftsManager.getContentTableName(collection);
9332
9469
  const prefixQuery = escapeQuery(query);
9333
9470
  if (!prefixQuery) continue;
9334
- const results = await sql`
9335
- SELECT
9336
- c.id,
9337
- c.title
9338
- FROM "${sql.raw(ftsTable)}" f
9339
- JOIN "${sql.raw(contentTable)}" c ON f.id = c.id
9340
- WHERE "${sql.raw(ftsTable)}" MATCH ${prefixQuery}
9341
- AND c.status = 'published'
9342
- AND c.deleted_at IS NULL
9343
- AND c.title IS NOT NULL
9344
- ${locale ? sql`AND c.locale = ${locale}` : sql``}
9345
- ORDER BY bm25("${sql.raw(ftsTable)}")
9346
- LIMIT ${limit}
9347
- `.execute(db);
9471
+ let results;
9472
+ try {
9473
+ results = await sql`
9474
+ SELECT
9475
+ c.id,
9476
+ c.title
9477
+ FROM "${sql.raw(ftsTable)}" f
9478
+ JOIN "${sql.raw(contentTable)}" c ON f.id = c.id
9479
+ WHERE "${sql.raw(ftsTable)}" MATCH ${prefixQuery}
9480
+ AND c.status = 'published'
9481
+ AND c.deleted_at IS NULL
9482
+ AND c.title IS NOT NULL
9483
+ ${locale ? sql`AND c.locale = ${locale}` : sql``}
9484
+ ORDER BY bm25("${sql.raw(ftsTable)}")
9485
+ LIMIT ${limit}
9486
+ `.execute(db);
9487
+ } catch (error) {
9488
+ if (isFts5SyntaxError(error)) continue;
9489
+ throw error;
9490
+ }
9348
9491
  for (const row of results.rows) suggestions.push({
9349
9492
  collection,
9350
9493
  id: row.id,
@@ -9490,5 +9633,5 @@ function extractSearchableFields(entry, fields) {
9490
9633
  }
9491
9634
 
9492
9635
  //#endregion
9493
- export { prosemirrorToPortableText as $, isStandardPluginDefinition as A, handleContentPublish as At, EmailPipeline as B, image as Bt, getAllSources as C, handleContentDiscardDraft as Ct, probeUrl as D, handleContentList as Dt, getUrlSources as E, handleContentGetIncludingTrashed as Et, createPluginManager as F, handleContentUnschedule as Ft, extractRequestMeta as G, createHookPipeline as H, PluginRouteError as I, handleContentUpdate as It, definePlugin as J, sanitizeHeadersForSandbox as K, PluginRouteRegistry as L, validateRev as Lt, SandboxNotAvailableError as M, handleContentSchedule as Mt, createNoopSandboxRunner as N, handleContentTranslations as Nt, registerSource as O, handleContentListTrashed as Ot, PluginManager as P, handleContentUnpublish as Pt, portableTextToProsemirror as Q, DEV_CONSOLE_EMAIL_PLUGIN_ID as R, portableText as Rt, clearSources as S, handleContentDelete as St, getSource as T, handleContentGet as Tt, resolveExclusiveHooks as U, HookPipeline as V, CronExecutor as W, parseWxrString as X, parseWxr as Y, after as Z, buildPreviewUrl as _, hashString as _t, search as a, PluginStateRepository as at, parseWxrDate as b, handleContentCountTrashed as bt, getWidgetArea as c, handleMediaDelete as ct, getMenu as d, handleMediaUpdate as dt, isSafeHref as et, getMenus as f, handleRevisionGet as ft, isPreviewRequest as g, computeContentHash as gt, getPreviewToken as h, generateManifest as ht, getSuggestions as i, getSections as it, NoopSandboxRunner as j, handleContentRestore as jt, importReusableBlocksAsSections as k, handleContentPermanentDelete as kt, getWidgetAreas as l, handleMediaGet as lt, getComments as m, handleRevisionRestore as mt, extractSearchableFields as n, loadBundleFromR2 as nt, searchCollection as o, getCollectionInfo as ot, getCommentCount as p, handleRevisionList as pt, getTrustedProxyHeaders as q, getSearchStats as r, getSection as rt, searchWithDb as s, handleMediaCreate as st, extractPlainText as t, sanitizeHref as tt, getWidgetComponents as u, handleMediaList as ut, getPreviewUrl as v, handleContentCompare as vt, getFileSources as w, handleContentDuplicate as wt, wxrSource as x, handleContentCreate as xt, wordpressRestSource as y, handleContentCountScheduled as yt, devConsoleEmailDeliver as z, reference as zt };
9494
- //# sourceMappingURL=search-B0effn3j.mjs.map
9636
+ export { prosemirrorToPortableText as $, isStandardPluginDefinition as A, handleContentSchedule as At, EmailPipeline as B, getAllSources as C, handleContentGet as Ct, probeUrl as D, handleContentPermanentDelete as Dt, getUrlSources as E, handleContentListTrashed as Et, createPluginManager as F, validateRev as Ft, extractRequestMeta as G, createHookPipeline as H, PluginRouteError as I, portableText as It, definePlugin as J, sanitizeHeadersForSandbox as K, PluginRouteRegistry as L, reference as Lt, SandboxNotAvailableError as M, handleContentUnpublish as Mt, createNoopSandboxRunner as N, handleContentUnschedule as Nt, registerSource as O, handleContentPublish as Ot, PluginManager as P, handleContentUpdate as Pt, portableTextToProsemirror as Q, DEV_CONSOLE_EMAIL_PLUGIN_ID as R, image as Rt, clearSources as S, handleContentDuplicate as St, getSource as T, handleContentList as Tt, resolveExclusiveHooks as U, HookPipeline as V, CronExecutor as W, parseWxrString as X, parseWxr as Y, after as Z, buildPreviewUrl as _, handleContentCountScheduled as _t, search as a, PluginStateRepository as at, parseWxrDate as b, handleContentDelete as bt, getWidgetArea as c, handleMediaDelete as ct, getMenu as d, handleMediaUpdate as dt, isSafeHref as et, getMenus as f, handleRevisionGet as ft, isPreviewRequest as g, handleContentCompare as gt, getPreviewToken as h, generateManifest as ht, getSuggestions as i, getSections as it, NoopSandboxRunner as j, handleContentTranslations as jt, importReusableBlocksAsSections as k, handleContentRestore as kt, getWidgetAreas as l, handleMediaGet as lt, getComments as m, handleRevisionRestore as mt, extractSearchableFields as n, loadBundleFromR2 as nt, searchCollection as o, getCollectionInfo as ot, getCommentCount as p, handleRevisionList as pt, getTrustedProxyHeaders as q, getSearchStats as r, getSection as rt, searchWithDb as s, handleMediaCreate as st, extractPlainText as t, sanitizeHref as tt, getWidgetComponents as u, handleMediaList as ut, getPreviewUrl as v, handleContentCountTrashed as vt, getFileSources as w, handleContentGetIncludingTrashed as wt, wxrSource as x, handleContentDiscardDraft as xt, wordpressRestSource as y, handleContentCreate as yt, devConsoleEmailDeliver as z };
9637
+ //# sourceMappingURL=search-BoZYFuUk.mjs.map