emdash 0.5.0 → 0.7.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 (252) hide show
  1. package/dist/{adapters-C2BzVy0p.d.mts → adapters-Di31kZ28.d.mts} +16 -1
  2. package/dist/adapters-Di31kZ28.d.mts.map +1 -0
  3. package/dist/{apply-Cma_PiF6.mjs → apply-5uslYdUu.mjs} +197 -25
  4. package/dist/apply-5uslYdUu.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 +203 -33
  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 +30 -4
  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.d.mts.map +1 -1
  15. package/dist/astro/middleware/request-context.mjs +11 -4
  16. package/dist/astro/middleware/request-context.mjs.map +1 -1
  17. package/dist/astro/middleware/setup.mjs +1 -1
  18. package/dist/astro/middleware.d.mts.map +1 -1
  19. package/dist/astro/middleware.mjs +467 -186
  20. package/dist/astro/middleware.mjs.map +1 -1
  21. package/dist/astro/types.d.mts +17 -9
  22. package/dist/astro/types.d.mts.map +1 -1
  23. package/dist/{byline-WuOq9MFJ.mjs → byline-C4OVd8b3.mjs} +3 -19
  24. package/dist/byline-C4OVd8b3.mjs.map +1 -0
  25. package/dist/{bylines-C_Wsnz4L.mjs → bylines-hPTW79hw.mjs} +20 -33
  26. package/dist/bylines-hPTW79hw.mjs.map +1 -0
  27. package/dist/{cache-E3Dts-yT.mjs → cache-BkKBuIvS.mjs} +1 -1
  28. package/dist/{cache-E3Dts-yT.mjs.map → cache-BkKBuIvS.mjs.map} +1 -1
  29. package/dist/chunks-HGz06Soa.mjs +19 -0
  30. package/dist/chunks-HGz06Soa.mjs.map +1 -0
  31. package/dist/cli/index.mjs +12 -11
  32. package/dist/cli/index.mjs.map +1 -1
  33. package/dist/client/cf-access.d.mts +1 -1
  34. package/dist/client/index.d.mts +1 -1
  35. package/dist/client/index.mjs +1 -1
  36. package/dist/{config-DkxPrM9l.mjs → config-BXwuX8Bx.mjs} +1 -1
  37. package/dist/{config-DkxPrM9l.mjs.map → config-BXwuX8Bx.mjs.map} +1 -1
  38. package/dist/{connection-B4zVnQIa.mjs → connection-2igzM-AT.mjs} +19 -2
  39. package/dist/connection-2igzM-AT.mjs.map +1 -0
  40. package/dist/{content-BsBoyj8G.mjs → content-D7J5y73J.mjs} +27 -1
  41. package/dist/{content-BsBoyj8G.mjs.map → content-D7J5y73J.mjs.map} +1 -1
  42. package/dist/database/instrumentation.d.mts +45 -0
  43. package/dist/database/instrumentation.d.mts.map +1 -0
  44. package/dist/database/instrumentation.mjs +61 -0
  45. package/dist/database/instrumentation.mjs.map +1 -0
  46. package/dist/db/index.d.mts +3 -3
  47. package/dist/db/index.mjs +1 -1
  48. package/dist/db/index.mjs.map +1 -1
  49. package/dist/db/libsql.d.mts +1 -1
  50. package/dist/db/postgres.d.mts +1 -1
  51. package/dist/db/sqlite.d.mts +1 -1
  52. package/dist/db-errors-D0UT85nC.mjs +41 -0
  53. package/dist/db-errors-D0UT85nC.mjs.map +1 -0
  54. package/dist/{default-PUx9RK6u.mjs → default-CME5YdZ3.mjs} +1 -1
  55. package/dist/{default-PUx9RK6u.mjs.map → default-CME5YdZ3.mjs.map} +1 -1
  56. package/dist/{error-HBeQbVhV.mjs → error-CiYn9yDu.mjs} +1 -1
  57. package/dist/{error-HBeQbVhV.mjs.map → error-CiYn9yDu.mjs.map} +1 -1
  58. package/dist/{index-CCWzlriB.d.mts → index-De6_Xv3v.d.mts} +209 -19
  59. package/dist/index-De6_Xv3v.d.mts.map +1 -0
  60. package/dist/index.d.mts +11 -11
  61. package/dist/index.mjs +23 -21
  62. package/dist/{load-BhSSm-TS.mjs → load-CBcmDIot.mjs} +1 -1
  63. package/dist/{load-BhSSm-TS.mjs.map → load-CBcmDIot.mjs.map} +1 -1
  64. package/dist/{loader-BYzwzORf.mjs → loader-DeiBJEMe.mjs} +18 -12
  65. package/dist/loader-DeiBJEMe.mjs.map +1 -0
  66. package/dist/{manifest-schema-BsXINkQD.mjs → manifest-schema-V30qsMft.mjs} +1 -1
  67. package/dist/{manifest-schema-BsXINkQD.mjs.map → manifest-schema-V30qsMft.mjs.map} +1 -1
  68. package/dist/media/index.d.mts +1 -1
  69. package/dist/media/index.mjs +1 -1
  70. package/dist/media/local-runtime.d.mts +7 -7
  71. package/dist/{mode-CyPLdO3C.mjs → mode-CpNnGkPz.mjs} +1 -1
  72. package/dist/{mode-CyPLdO3C.mjs.map → mode-CpNnGkPz.mjs.map} +1 -1
  73. package/dist/page/index.d.mts +11 -2
  74. package/dist/page/index.d.mts.map +1 -1
  75. package/dist/page/index.mjs +23 -1
  76. package/dist/page/index.mjs.map +1 -1
  77. package/dist/{placeholder-DntBEQo7.mjs → placeholder-C-fk5hYI.mjs} +1 -1
  78. package/dist/{placeholder-DntBEQo7.mjs.map → placeholder-C-fk5hYI.mjs.map} +1 -1
  79. package/dist/{placeholder-BBCtpTES.d.mts → placeholder-tzpqGWII.d.mts} +1 -1
  80. package/dist/{placeholder-BBCtpTES.d.mts.map → placeholder-tzpqGWII.d.mts.map} +1 -1
  81. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  82. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  83. package/dist/{query-B6Vu0d2i.mjs → query-g4Ug-9j9.mjs} +79 -12
  84. package/dist/query-g4Ug-9j9.mjs.map +1 -0
  85. package/dist/{redirect-7lGhLBNZ.mjs → redirect-CN0Rt9Ob.mjs} +66 -10
  86. package/dist/redirect-CN0Rt9Ob.mjs.map +1 -0
  87. package/dist/{registry-BgnP3ysR.mjs → registry-Ci3WxVAr.mjs} +133 -97
  88. package/dist/registry-Ci3WxVAr.mjs.map +1 -0
  89. package/dist/request-cache-DiR961CV.mjs +79 -0
  90. package/dist/request-cache-DiR961CV.mjs.map +1 -0
  91. package/dist/request-context.d.mts +19 -16
  92. package/dist/request-context.d.mts.map +1 -1
  93. package/dist/request-context.mjs.map +1 -1
  94. package/dist/{runner-DYv3rX8P.d.mts → runner-BR2xKwhn.d.mts} +2 -2
  95. package/dist/{runner-DYv3rX8P.d.mts.map → runner-BR2xKwhn.d.mts.map} +1 -1
  96. package/dist/{runner-Cd-_WyDo.mjs → runner-tQ7BJ4T7.mjs} +211 -134
  97. package/dist/runner-tQ7BJ4T7.mjs.map +1 -0
  98. package/dist/runtime.d.mts +6 -6
  99. package/dist/runtime.mjs +1 -1
  100. package/dist/{search-Cn1SYvYF.mjs → search-B0effn3j.mjs} +210 -226
  101. package/dist/search-B0effn3j.mjs.map +1 -0
  102. package/dist/seed/index.d.mts +2 -2
  103. package/dist/seed/index.mjs +10 -9
  104. package/dist/seo/index.d.mts +1 -1
  105. package/dist/storage/local.d.mts +1 -1
  106. package/dist/storage/local.mjs +1 -1
  107. package/dist/storage/s3.d.mts +1 -1
  108. package/dist/storage/s3.mjs +1 -1
  109. package/dist/taxonomies-K2z0Uhnj.mjs +308 -0
  110. package/dist/taxonomies-K2z0Uhnj.mjs.map +1 -0
  111. package/dist/{tokens-DKHiCYCB.mjs → tokens-BFPFx3CA.mjs} +1 -1
  112. package/dist/{tokens-DKHiCYCB.mjs.map → tokens-BFPFx3CA.mjs.map} +1 -1
  113. package/dist/{transport-BtcQ-Z7T.mjs → transport-BykRfpyy.mjs} +1 -1
  114. package/dist/{transport-BtcQ-Z7T.mjs.map → transport-BykRfpyy.mjs.map} +1 -1
  115. package/dist/{transport-CKQA_G44.d.mts → transport-H4Iwx7tC.d.mts} +1 -1
  116. package/dist/{transport-CKQA_G44.d.mts.map → transport-H4Iwx7tC.d.mts.map} +1 -1
  117. package/dist/{types-BmkQR1En.d.mts → types-6CUZRrZP.d.mts} +1 -1
  118. package/dist/{types-BmkQR1En.d.mts.map → types-6CUZRrZP.d.mts.map} +1 -1
  119. package/dist/{types-Dz9_WMS6.mjs → types-BH2L167P.mjs} +1 -1
  120. package/dist/{types-Dz9_WMS6.mjs.map → types-BH2L167P.mjs.map} +1 -1
  121. package/dist/{types-B6BzlZxx.d.mts → types-C2v0c34j.d.mts} +10 -1
  122. package/dist/{types-B6BzlZxx.d.mts.map → types-C2v0c34j.d.mts.map} +1 -1
  123. package/dist/{types-DNZpaCBk.d.mts → types-CFWjXmus.d.mts} +1 -1
  124. package/dist/{types-DNZpaCBk.d.mts.map → types-CFWjXmus.d.mts.map} +1 -1
  125. package/dist/{types-DeG21anB.d.mts → types-CnZYHyLW.d.mts} +55 -5
  126. package/dist/types-CnZYHyLW.d.mts.map +1 -0
  127. package/dist/{types-xxCWI3j0.mjs → types-DDS4MxsT.mjs} +11 -3
  128. package/dist/types-DDS4MxsT.mjs.map +1 -0
  129. package/dist/{types-C3ronwXb.d.mts → types-DgrIP0tF.d.mts} +102 -4
  130. package/dist/types-DgrIP0tF.d.mts.map +1 -0
  131. package/dist/{validate-DuZDIxfy.mjs → validate-CqsNItbt.mjs} +2 -2
  132. package/dist/{validate-DuZDIxfy.mjs.map → validate-CqsNItbt.mjs.map} +1 -1
  133. package/dist/{validate-Db1yNL3i.d.mts → validate-kM8Pjuf7.d.mts} +5 -52
  134. package/dist/validate-kM8Pjuf7.d.mts.map +1 -0
  135. package/dist/version-BnTKdfam.mjs +7 -0
  136. package/dist/{version-CMMjTuqu.mjs.map → version-BnTKdfam.mjs.map} +1 -1
  137. package/package.json +10 -5
  138. package/src/after.ts +62 -0
  139. package/src/api/handlers/content.ts +2 -0
  140. package/src/api/handlers/oauth-authorization.ts +2 -32
  141. package/src/api/handlers/oauth-clients.ts +40 -4
  142. package/src/api/handlers/taxonomies.ts +13 -0
  143. package/src/api/oauth/redirect-uri.ts +34 -0
  144. package/src/api/openapi/document.ts +126 -118
  145. package/src/api/schemas/content.ts +8 -0
  146. package/src/api/schemas/media.ts +26 -15
  147. package/src/api/schemas/schema.ts +1 -0
  148. package/src/astro/integration/font-provider.ts +178 -0
  149. package/src/astro/integration/index.ts +44 -0
  150. package/src/astro/integration/routes.ts +6 -0
  151. package/src/astro/integration/runtime.ts +117 -0
  152. package/src/astro/integration/virtual-modules.ts +41 -39
  153. package/src/astro/integration/vite-config.ts +16 -5
  154. package/src/astro/middleware/auth.ts +33 -1
  155. package/src/astro/middleware/request-context.ts +15 -3
  156. package/src/astro/middleware.ts +340 -263
  157. package/src/astro/routes/admin.astro +21 -10
  158. package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
  159. package/src/astro/routes/api/auth/passkey/options.ts +2 -1
  160. package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
  161. package/src/astro/routes/api/auth/signup/request.ts +26 -8
  162. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +10 -6
  163. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
  164. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
  165. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
  166. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +5 -0
  167. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +26 -0
  168. package/src/astro/routes/api/content/[collection]/[id].ts +30 -2
  169. package/src/astro/routes/api/content/[collection]/index.ts +19 -1
  170. package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
  171. package/src/astro/routes/api/import/wordpress/execute.ts +1 -1
  172. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +4 -3
  173. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +5 -4
  174. package/src/astro/routes/api/manifest.ts +7 -0
  175. package/src/astro/routes/api/media/upload-url.ts +10 -2
  176. package/src/astro/routes/api/media.ts +10 -7
  177. package/src/astro/routes/api/oauth/device/code.ts +2 -1
  178. package/src/astro/routes/api/oauth/device/token.ts +2 -1
  179. package/src/astro/routes/api/oauth/register.ts +178 -0
  180. package/src/astro/routes/api/oauth/token.ts +15 -0
  181. package/src/astro/routes/api/openapi.json.ts +15 -5
  182. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +2 -0
  183. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +1 -0
  184. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +1 -0
  185. package/src/astro/routes/api/search/index.ts +5 -0
  186. package/src/astro/routes/api/search/suggest.ts +3 -0
  187. package/src/astro/routes/api/setup/admin-verify.ts +30 -5
  188. package/src/astro/routes/api/setup/admin.ts +32 -8
  189. package/src/astro/routes/api/setup/index.ts +5 -2
  190. package/src/astro/routes/api/taxonomies/index.ts +1 -0
  191. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +1 -1
  192. package/src/astro/types.ts +9 -0
  193. package/src/auth/rate-limit.ts +50 -22
  194. package/src/auth/setup-nonce.ts +22 -0
  195. package/src/auth/trusted-proxy.ts +92 -0
  196. package/src/bylines/index.ts +22 -45
  197. package/src/components/EmDashHead.astro +23 -7
  198. package/src/database/connection.ts +23 -1
  199. package/src/database/instrumentation.ts +98 -0
  200. package/src/database/migrations/035_bounded_404_log.ts +112 -0
  201. package/src/database/migrations/runner.ts +2 -0
  202. package/src/database/repositories/content.ts +39 -0
  203. package/src/database/repositories/options.ts +25 -0
  204. package/src/database/repositories/redirect.ts +111 -8
  205. package/src/database/types.ts +9 -0
  206. package/src/db/adapters.ts +15 -0
  207. package/src/emdash-runtime.ts +312 -92
  208. package/src/import/registry.ts +4 -3
  209. package/src/import/ssrf.ts +253 -12
  210. package/src/index.ts +6 -0
  211. package/src/loader.ts +19 -24
  212. package/src/mcp/server.ts +76 -3
  213. package/src/menus/index.ts +6 -3
  214. package/src/page/index.ts +1 -1
  215. package/src/page/seo-contributions.ts +36 -0
  216. package/src/plugins/context.ts +15 -3
  217. package/src/plugins/manager.ts +6 -0
  218. package/src/plugins/request-meta.ts +66 -15
  219. package/src/plugins/routes.ts +3 -1
  220. package/src/query.ts +104 -7
  221. package/src/request-cache.ts +106 -0
  222. package/src/request-context.ts +19 -0
  223. package/src/schema/query.ts +5 -2
  224. package/src/schema/registry.ts +243 -166
  225. package/src/schema/types.ts +13 -2
  226. package/src/schema/zod-generator.ts +4 -0
  227. package/src/search/fts-manager.ts +19 -5
  228. package/src/search/query.ts +4 -3
  229. package/src/seed/apply.ts +41 -1
  230. package/src/settings/index.ts +24 -5
  231. package/src/taxonomies/index.ts +324 -124
  232. package/src/utils/db-errors.ts +46 -0
  233. package/src/virtual-modules.d.ts +31 -10
  234. package/src/visual-editing/toolbar.ts +6 -1
  235. package/src/widgets/index.ts +54 -25
  236. package/dist/adapters-C2BzVy0p.d.mts.map +0 -1
  237. package/dist/apply-Cma_PiF6.mjs.map +0 -1
  238. package/dist/byline-WuOq9MFJ.mjs.map +0 -1
  239. package/dist/bylines-C_Wsnz4L.mjs.map +0 -1
  240. package/dist/connection-B4zVnQIa.mjs.map +0 -1
  241. package/dist/index-CCWzlriB.d.mts.map +0 -1
  242. package/dist/loader-BYzwzORf.mjs.map +0 -1
  243. package/dist/query-B6Vu0d2i.mjs.map +0 -1
  244. package/dist/redirect-7lGhLBNZ.mjs.map +0 -1
  245. package/dist/registry-BgnP3ysR.mjs.map +0 -1
  246. package/dist/runner-Cd-_WyDo.mjs.map +0 -1
  247. package/dist/search-Cn1SYvYF.mjs.map +0 -1
  248. package/dist/types-C3ronwXb.d.mts.map +0 -1
  249. package/dist/types-DeG21anB.d.mts.map +0 -1
  250. package/dist/types-xxCWI3j0.mjs.map +0 -1
  251. package/dist/validate-Db1yNL3i.d.mts.map +0 -1
  252. package/dist/version-CMMjTuqu.mjs +0 -7
@@ -1,23 +1,25 @@
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-BsBoyj8G.mjs";
3
+ import { a as slugify, r as RevisionRepository, t as ContentRepository } from "./content-D7J5y73J.mjs";
4
4
  import { r as encodeBase64, t as decodeBase64 } from "./base64-MBPo9ozB.mjs";
5
5
  import { n as decodeCursor, r as encodeCursor, t as EmDashValidationError } from "./types-CMMN0pNg.mjs";
6
6
  import { t as MediaRepository } from "./media-DqHVh136.mjs";
7
- import { a as stripCredentialHeaders, f as OptionsRepository, i as ssrfSafeFetch, o as validateExternalUrl, r as SsrfError } from "./apply-Cma_PiF6.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";
8
8
  import { t as withTransaction } from "./transaction-Cn2rjY78.mjs";
9
- import { t as RedirectRepository } from "./redirect-7lGhLBNZ.mjs";
10
- import { n as SQL_BATCH_SIZE, r as chunks, t as BylineRepository } from "./byline-WuOq9MFJ.mjs";
11
- import { r as isI18nEnabled } from "./config-DkxPrM9l.mjs";
12
- import { r as invalidateRedirectCache } from "./cache-E3Dts-yT.mjs";
13
- import { i as FTSManager, n as SchemaRegistry } from "./registry-BgnP3ysR.mjs";
14
- import { n as getDb } from "./loader-BYzwzORf.mjs";
15
- import { i as pluginManifestSchema } from "./manifest-schema-BsXINkQD.mjs";
16
- import { t as generatePreviewToken } from "./tokens-DKHiCYCB.mjs";
9
+ import { t as RedirectRepository } from "./redirect-CN0Rt9Ob.mjs";
10
+ import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-HGz06Soa.mjs";
11
+ import { t as BylineRepository } from "./byline-C4OVd8b3.mjs";
12
+ import { r as isI18nEnabled } from "./config-BXwuX8Bx.mjs";
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";
17
19
  import { sql } from "kysely";
20
+ import { AsyncLocalStorage } from "node:async_hooks";
18
21
  import { ulid } from "ulidx";
19
22
  import { z } from "astro/zod";
20
- import { AsyncLocalStorage } from "node:async_hooks";
21
23
  import { z as z$1 } from "zod";
22
24
  import { createGzipDecoder, unpackTar } from "modern-tar";
23
25
  import sax from "sax";
@@ -1296,7 +1298,8 @@ async function handleContentUpdate(db, collection, id, body) {
1296
1298
  data: body.data,
1297
1299
  slug: body.slug,
1298
1300
  status: body.status,
1299
- authorId: body.authorId
1301
+ authorId: body.authorId,
1302
+ publishedAt: body.publishedAt
1300
1303
  });
1301
1304
  if (body.bylines !== void 0) {
1302
1305
  await bylineRepo.setContentBylines(collection, resolvedId, body.bylines);
@@ -2229,7 +2232,9 @@ async function handleMediaDelete(db, id) {
2229
2232
  * ```
2230
2233
  */
2231
2234
  async function getCollectionInfo(slug) {
2232
- return getCollectionInfoWithDb(await getDb(), slug);
2235
+ return requestCached(`collection-info:${slug}`, async () => {
2236
+ return getCollectionInfoWithDb(await getDb(), slug);
2237
+ });
2233
2238
  }
2234
2239
  /**
2235
2240
  * Get collection metadata with an explicit db handle.
@@ -2636,6 +2641,11 @@ const contentListQuery = cursorPaginationQuery.extend({
2636
2641
  order: z$1.enum(["asc", "desc"]).optional(),
2637
2642
  locale: localeCode.optional()
2638
2643
  }).meta({ id: "ContentListQuery" });
2644
+ /** ISO 8601 datetime for `publishedAt` / `createdAt`. Routes gate writes behind `content:publish_any`. */
2645
+ const contentDateOverride = z$1.iso.datetime({
2646
+ offset: true,
2647
+ message: "must be an ISO 8601 datetime"
2648
+ }).nullish();
2639
2649
  const contentCreateBody = z$1.object({
2640
2650
  data: z$1.record(z$1.string(), z$1.unknown()),
2641
2651
  slug: z$1.string().nullish(),
@@ -2643,7 +2653,9 @@ const contentCreateBody = z$1.object({
2643
2653
  bylines: z$1.array(contentBylineInputSchema).optional(),
2644
2654
  locale: localeCode.optional(),
2645
2655
  translationOf: z$1.string().optional(),
2646
- seo: contentSeoInput.optional()
2656
+ seo: contentSeoInput.optional(),
2657
+ publishedAt: contentDateOverride,
2658
+ createdAt: contentDateOverride
2647
2659
  }).meta({ id: "ContentCreateBody" });
2648
2660
  const contentUpdateBody = z$1.object({
2649
2661
  data: z$1.record(z$1.string(), z$1.unknown()).optional(),
@@ -2653,7 +2665,8 @@ const contentUpdateBody = z$1.object({
2653
2665
  bylines: z$1.array(contentBylineInputSchema).optional(),
2654
2666
  _rev: z$1.string().optional().meta({ description: "Opaque revision token for optimistic concurrency" }),
2655
2667
  skipRevision: z$1.boolean().optional(),
2656
- seo: contentSeoInput.optional()
2668
+ seo: contentSeoInput.optional(),
2669
+ publishedAt: contentDateOverride
2657
2670
  }).meta({ id: "ContentUpdateBody" });
2658
2671
  const contentScheduleBody = z$1.object({ scheduledAt: z$1.string().min(1, "scheduledAt is required").meta({
2659
2672
  description: "ISO 8601 datetime for scheduled publishing",
@@ -2751,14 +2764,8 @@ const mediaUpdateBody = z$1.object({
2751
2764
  width: z$1.number().int().positive().optional(),
2752
2765
  height: z$1.number().int().positive().optional()
2753
2766
  }).meta({ id: "MediaUpdateBody" });
2754
- /** Maximum allowed file upload size (50 MB). */
2755
- const MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
2756
- const mediaUploadUrlBody = z$1.object({
2757
- filename: z$1.string().min(1, "filename is required"),
2758
- contentType: z$1.string().min(1, "contentType is required"),
2759
- size: z$1.number().int().positive().max(MAX_UPLOAD_SIZE, `File size must not exceed ${MAX_UPLOAD_SIZE / 1024 / 1024}MB`),
2760
- contentHash: z$1.string().optional()
2761
- }).meta({ id: "MediaUploadUrlBody" });
2767
+ /** Default maximum allowed file upload size (50 MB). */
2768
+ const DEFAULT_MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
2762
2769
  const mediaConfirmBody = z$1.object({
2763
2770
  size: z$1.number().int().positive().optional(),
2764
2771
  width: z$1.number().int().positive().optional(),
@@ -2824,6 +2831,7 @@ const collectionSourcePattern = /^(template:.+|import:.+|manual|discovered|seed)
2824
2831
  const fieldTypeValues = z$1.enum([
2825
2832
  "string",
2826
2833
  "text",
2834
+ "url",
2827
2835
  "number",
2828
2836
  "integer",
2829
2837
  "boolean",
@@ -4221,6 +4229,33 @@ function convertCodeBlock(block) {
4221
4229
  };
4222
4230
  }
4223
4231
 
4232
+ //#endregion
4233
+ //#region src/after.ts
4234
+ const waitUntilReady = (async () => {
4235
+ try {
4236
+ return (await import("virtual:emdash/wait-until")).waitUntil ?? null;
4237
+ } catch {
4238
+ return null;
4239
+ }
4240
+ })();
4241
+ waitUntilReady.catch(() => {});
4242
+ /**
4243
+ * Schedule `fn` to run without blocking the response.
4244
+ *
4245
+ * Errors are caught and logged — a deferred task should never surface
4246
+ * as an unhandled rejection because the response is long gone. Callers
4247
+ * that care about errors should handle them inside `fn`.
4248
+ */
4249
+ function after(fn) {
4250
+ const promise = Promise.resolve().then(fn).catch((error) => {
4251
+ console.error("[emdash] deferred task failed:", error);
4252
+ });
4253
+ waitUntilReady.then((waitUntil) => {
4254
+ if (waitUntil) waitUntil(promise);
4255
+ return null;
4256
+ });
4257
+ }
4258
+
4224
4259
  //#endregion
4225
4260
  //#region src/cli/wxr/parser.ts
4226
4261
  const PHP_SERIALIZED_STRING_PATTERN = /s:\d+:"([^"]+)"/g;
@@ -4957,6 +4992,55 @@ function resolveHook(hook, pluginId) {
4957
4992
  };
4958
4993
  }
4959
4994
 
4995
+ //#endregion
4996
+ //#region src/auth/trusted-proxy.ts
4997
+ /**
4998
+ * RFC 7230 token — valid characters for an HTTP header name. Invalid names
4999
+ * passed to `Headers.get()` throw a TypeError at runtime, which would
5000
+ * otherwise surface as a 500 from every auth route.
5001
+ */
5002
+ const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9a-z]+$/;
5003
+ /**
5004
+ * Normalise a list of header names the way both the config path and any
5005
+ * caller passing a pre-resolved list should do: trim, lowercase, drop
5006
+ * empty, drop anything that isn't a valid RFC 7230 token. Invalid names
5007
+ * would crash `Headers.get()` at runtime.
5008
+ */
5009
+ function normalizeTrustedHeaders(names) {
5010
+ return names.map((h) => h.trim().toLowerCase()).filter((h) => h.length > 0 && HEADER_NAME_PATTERN.test(h));
5011
+ }
5012
+ function isValidHeaderName(name) {
5013
+ return HEADER_NAME_PATTERN.test(name);
5014
+ }
5015
+ /** Cache for the env-derived value. `null` means "not yet parsed". */
5016
+ let _envCache = null;
5017
+ function getEnvTrustedHeaders() {
5018
+ if (_envCache !== null) return _envCache;
5019
+ let raw;
5020
+ try {
5021
+ const importMetaEnv = import.meta.env;
5022
+ raw = (typeof process !== "undefined" ? process.env?.EMDASH_TRUSTED_PROXY_HEADERS : void 0) || importMetaEnv?.EMDASH_TRUSTED_PROXY_HEADERS;
5023
+ } catch {
5024
+ raw = void 0;
5025
+ }
5026
+ if (!raw) {
5027
+ _envCache = [];
5028
+ return _envCache;
5029
+ }
5030
+ _envCache = raw.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0 && isValidHeaderName(s));
5031
+ return _envCache;
5032
+ }
5033
+ /**
5034
+ * Return the lowercased list of headers to trust for client-IP resolution.
5035
+ *
5036
+ * When `config?.trustedProxyHeaders` is explicitly set (even to `[]`), it
5037
+ * wins. Otherwise fall through to the env var, then to `[]`.
5038
+ */
5039
+ function getTrustedProxyHeaders(config) {
5040
+ if (config && config.trustedProxyHeaders !== void 0) return config.trustedProxyHeaders.map((h) => h.trim().toLowerCase()).filter((h) => h.length > 0 && isValidHeaderName(h));
5041
+ return getEnvTrustedHeaders();
5042
+ }
5043
+
4960
5044
  //#endregion
4961
5045
  //#region src/plugins/request-meta.ts
4962
5046
  /**
@@ -4978,6 +5062,20 @@ function parseFirstForwardedIp(header) {
4978
5062
  return IP_PATTERN.test(trimmed) ? trimmed : null;
4979
5063
  }
4980
5064
  /**
5065
+ * Read an IP from an operator-declared trusted header. XFF-style headers
5066
+ * (any name ending in `forwarded-for`) are parsed as comma-separated lists
5067
+ * and the first entry is used; everything else is treated as a single
5068
+ * trimmed value.
5069
+ */
5070
+ function readIpFromHeader(headers, name) {
5071
+ const value = headers.get(name);
5072
+ if (!value) return null;
5073
+ if (name.endsWith("forwarded-for")) return parseFirstForwardedIp(value);
5074
+ const trimmed = value.trim();
5075
+ if (!trimmed) return null;
5076
+ return IP_PATTERN.test(trimmed) ? trimmed : null;
5077
+ }
5078
+ /**
4981
5079
  * Get the Cloudflare `cf` object from the request, if present.
4982
5080
  * Returns undefined when not running on Cloudflare Workers.
4983
5081
  */
@@ -5004,24 +5102,39 @@ function extractGeo(cf) {
5004
5102
  * Extract normalized request metadata from a Request object.
5005
5103
  *
5006
5104
  * IP resolution order:
5007
- * 1. `CF-Connecting-IP` header — only trusted when a `cf` object is
5008
- * present on the request (proving the request came through Cloudflare's
5009
- * edge, which strips/overwrites client-supplied values).
5010
- * 2. `X-Forwarded-For` header (first entry) — best-effort, spoofable
5011
- * when there is no trusted reverse proxy.
5012
- * 3. `null`
5013
- */
5014
- function extractRequestMeta(request) {
5105
+ * 1. `CF-Connecting-IP` — trusted only when a `cf` object is present on the
5106
+ * request. CF edge overwrites any client-supplied value, so this is the
5107
+ * cryptographically trustworthy path on Workers. Operator-declared
5108
+ * trusted headers cannot override it.
5109
+ * 2. `X-Forwarded-For` first entry — trusted only with a `cf` object.
5110
+ * 3. Operator-declared trusted proxy headers (from `config.trustedProxyHeaders`
5111
+ * or the `EMDASH_TRUSTED_PROXY_HEADERS` env var), tried in order. Used as
5112
+ * the primary source off-CF and as a fill-in on CF.
5113
+ * 4. `null`
5114
+ *
5115
+ * The second argument accepts either the EmDash config or a pre-resolved
5116
+ * list of trusted headers, so callers that already have the list don't have
5117
+ * to round-trip through the config every request.
5118
+ */
5119
+ function extractRequestMeta(request, configOrTrustedHeaders) {
5015
5120
  const headers = request.headers;
5016
5121
  const cf = getCfObject(request);
5122
+ const trusted = resolveTrustedHeaders(configOrTrustedHeaders);
5017
5123
  let ip = null;
5018
5124
  if (cf) {
5019
5125
  const cfIp = headers.get("cf-connecting-ip")?.trim();
5020
5126
  if (cfIp && IP_PATTERN.test(cfIp)) ip = cfIp;
5127
+ if (!ip) {
5128
+ const xff = headers.get("x-forwarded-for");
5129
+ ip = xff ? parseFirstForwardedIp(xff) : null;
5130
+ }
5021
5131
  }
5022
- if (!ip && cf) {
5023
- const xff = headers.get("x-forwarded-for");
5024
- ip = xff ? parseFirstForwardedIp(xff) : null;
5132
+ if (!ip) for (const name of trusted) {
5133
+ const value = readIpFromHeader(headers, name);
5134
+ if (value) {
5135
+ ip = value;
5136
+ break;
5137
+ }
5025
5138
  }
5026
5139
  const userAgent = headers.get("user-agent")?.trim() || null;
5027
5140
  const referer = headers.get("referer")?.trim() || null;
@@ -5033,6 +5146,10 @@ function extractRequestMeta(request) {
5033
5146
  geo
5034
5147
  };
5035
5148
  }
5149
+ function resolveTrustedHeaders(value) {
5150
+ if (Array.isArray(value)) return normalizeTrustedHeaders(value);
5151
+ return getTrustedProxyHeaders(value);
5152
+ }
5036
5153
  /**
5037
5154
  * Headers that must never cross the RPC boundary to sandboxed plugins.
5038
5155
  * Session tokens, auth credentials, and infrastructure headers are stripped
@@ -5681,7 +5798,7 @@ function createUnrestrictedHttpAccess(pluginId) {
5681
5798
  let currentInit = init;
5682
5799
  for (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {
5683
5800
  try {
5684
- validateExternalUrl(currentUrl);
5801
+ await resolveAndValidateExternalUrl(currentUrl);
5685
5802
  } catch (e) {
5686
5803
  const msg = e instanceof SsrfError ? e.message : "SSRF validation failed";
5687
5804
  throw new Error(`Plugin "${pluginId}": blocked fetch to "${new URL(currentUrl).hostname}": ${msg}`, { cause: e });
@@ -6975,9 +7092,11 @@ async function devConsoleEmailDeliver(event, _ctx) {
6975
7092
  var PluginRouteHandler = class {
6976
7093
  contextFactory;
6977
7094
  plugin;
7095
+ trustedProxyHeaders;
6978
7096
  constructor(plugin, factoryOptions) {
6979
7097
  this.plugin = plugin;
6980
7098
  this.contextFactory = new PluginContextFactory(factoryOptions);
7099
+ this.trustedProxyHeaders = factoryOptions.trustedProxyHeaders ?? [];
6981
7100
  }
6982
7101
  /**
6983
7102
  * Invoke a route by name
@@ -7010,7 +7129,7 @@ var PluginRouteHandler = class {
7010
7129
  ...this.contextFactory.createContext(this.plugin),
7011
7130
  input: validatedInput,
7012
7131
  request: options.request,
7013
- requestMeta: extractRequestMeta(options.request)
7132
+ requestMeta: extractRequestMeta(options.request, this.trustedProxyHeaders)
7014
7133
  };
7015
7134
  try {
7016
7135
  return {
@@ -7189,7 +7308,8 @@ var PluginManager = class {
7189
7308
  this.factoryOptions = {
7190
7309
  db: options.db,
7191
7310
  storage: options.storage,
7192
- getUploadUrl: options.getUploadUrl
7311
+ getUploadUrl: options.getUploadUrl,
7312
+ trustedProxyHeaders: options.trustedProxyHeaders
7193
7313
  };
7194
7314
  }
7195
7315
  /**
@@ -7686,7 +7806,7 @@ async function probeUrl(url) {
7686
7806
  let normalizedUrl = url.trim();
7687
7807
  if (!normalizedUrl.startsWith("http")) normalizedUrl = `https://${normalizedUrl}`;
7688
7808
  normalizedUrl = normalizedUrl.replace(TRAILING_SLASHES_PATTERN, "");
7689
- validateExternalUrl(normalizedUrl);
7809
+ await resolveAndValidateExternalUrl(normalizedUrl);
7690
7810
  const results = [];
7691
7811
  const probePromises = getUrlSources().map(async (source) => {
7692
7812
  try {
@@ -8682,8 +8802,10 @@ async function getCommentCountWithDb(db, collection, contentId) {
8682
8802
  * }
8683
8803
  * ```
8684
8804
  */
8685
- async function getMenu(name) {
8686
- return getMenuWithDb(name, await getDb());
8805
+ function getMenu(name) {
8806
+ return requestCached(`menu:${name}`, async () => {
8807
+ return getMenuWithDb(name, await getDb());
8808
+ });
8687
8809
  }
8688
8810
  /**
8689
8811
  * Get menu by name with resolved URLs (with explicit db)
@@ -8850,179 +8972,6 @@ async function resolveTaxonomyUrl(taxonomyId, db) {
8850
8972
  return `/${taxonomy.name}/${taxonomy.slug}`;
8851
8973
  }
8852
8974
 
8853
- //#endregion
8854
- //#region src/taxonomies/index.ts
8855
- /**
8856
- * Runtime API for taxonomies
8857
- *
8858
- * Provides functions to query taxonomy definitions and terms.
8859
- */
8860
- /**
8861
- * Get all taxonomy definitions
8862
- */
8863
- async function getTaxonomyDefs() {
8864
- return (await (await getDb()).selectFrom("_emdash_taxonomy_defs").selectAll().execute()).map((row) => ({
8865
- id: row.id,
8866
- name: row.name,
8867
- label: row.label,
8868
- labelSingular: row.label_singular ?? void 0,
8869
- hierarchical: row.hierarchical === 1,
8870
- collections: row.collections ? JSON.parse(row.collections) : []
8871
- }));
8872
- }
8873
- /**
8874
- * Get a single taxonomy definition by name
8875
- */
8876
- async function getTaxonomyDef(name) {
8877
- const row = await (await getDb()).selectFrom("_emdash_taxonomy_defs").selectAll().where("name", "=", name).executeTakeFirst();
8878
- if (!row) return null;
8879
- return {
8880
- id: row.id,
8881
- name: row.name,
8882
- label: row.label,
8883
- labelSingular: row.label_singular ?? void 0,
8884
- hierarchical: row.hierarchical === 1,
8885
- collections: row.collections ? JSON.parse(row.collections) : []
8886
- };
8887
- }
8888
- /**
8889
- * Get all terms for a taxonomy (as tree for hierarchical, flat for tags)
8890
- */
8891
- async function getTaxonomyTerms(taxonomyName) {
8892
- const db = await getDb();
8893
- const def = await getTaxonomyDef(taxonomyName);
8894
- if (!def) return [];
8895
- const rows = await db.selectFrom("taxonomies").selectAll().where("name", "=", taxonomyName).orderBy("label", "asc").execute();
8896
- const countsResult = await db.selectFrom("content_taxonomies").select(["taxonomy_id"]).select((eb) => eb.fn.count("entry_id").as("count")).groupBy("taxonomy_id").execute();
8897
- const counts = /* @__PURE__ */ new Map();
8898
- for (const row of countsResult) counts.set(row.taxonomy_id, row.count);
8899
- const flatTerms = rows.map((row) => ({
8900
- id: row.id,
8901
- name: row.name,
8902
- slug: row.slug,
8903
- label: row.label,
8904
- parent_id: row.parent_id,
8905
- data: row.data
8906
- }));
8907
- if (def.hierarchical) return buildTree(flatTerms, counts);
8908
- return flatTerms.map((term) => ({
8909
- id: term.id,
8910
- name: term.name,
8911
- slug: term.slug,
8912
- label: term.label,
8913
- children: [],
8914
- count: counts.get(term.id) ?? 0
8915
- }));
8916
- }
8917
- /**
8918
- * Get a single term by taxonomy and slug
8919
- */
8920
- async function getTerm(taxonomyName, slug) {
8921
- const db = await getDb();
8922
- const row = await db.selectFrom("taxonomies").selectAll().where("name", "=", taxonomyName).where("slug", "=", slug).executeTakeFirst();
8923
- if (!row) return null;
8924
- const count = (await db.selectFrom("content_taxonomies").select((eb) => eb.fn.count("entry_id").as("count")).where("taxonomy_id", "=", row.id).executeTakeFirst())?.count ?? 0;
8925
- const children = (await db.selectFrom("taxonomies").selectAll().where("parent_id", "=", row.id).orderBy("label", "asc").execute()).map((child) => ({
8926
- id: child.id,
8927
- name: child.name,
8928
- slug: child.slug,
8929
- label: child.label,
8930
- parentId: child.parent_id ?? void 0,
8931
- children: []
8932
- }));
8933
- return {
8934
- id: row.id,
8935
- name: row.name,
8936
- slug: row.slug,
8937
- label: row.label,
8938
- parentId: row.parent_id ?? void 0,
8939
- description: row.data ? JSON.parse(row.data).description : void 0,
8940
- children,
8941
- count
8942
- };
8943
- }
8944
- /**
8945
- * Get terms assigned to an entry
8946
- */
8947
- async function getEntryTerms(collection, entryId, taxonomyName) {
8948
- let query = (await getDb()).selectFrom("content_taxonomies").innerJoin("taxonomies", "taxonomies.id", "content_taxonomies.taxonomy_id").selectAll("taxonomies").where("content_taxonomies.collection", "=", collection).where("content_taxonomies.entry_id", "=", entryId);
8949
- if (taxonomyName) query = query.where("taxonomies.name", "=", taxonomyName);
8950
- return (await query.execute()).map((row) => ({
8951
- id: row.id,
8952
- name: row.name,
8953
- slug: row.slug,
8954
- label: row.label,
8955
- parentId: row.parent_id ?? void 0,
8956
- children: []
8957
- }));
8958
- }
8959
- /**
8960
- * Get terms for multiple entries in a single query (batched API)
8961
- *
8962
- * This is more efficient than calling getEntryTerms for each entry
8963
- * when you need terms for a list of entries.
8964
- *
8965
- * @param collection - The collection type (e.g., "posts")
8966
- * @param entryIds - Array of entry IDs
8967
- * @param taxonomyName - The taxonomy name (e.g., "categories")
8968
- * @returns Map from entry ID to array of terms
8969
- */
8970
- async function getTermsForEntries(collection, entryIds, taxonomyName) {
8971
- const result = /* @__PURE__ */ new Map();
8972
- for (const id of entryIds) result.set(id, []);
8973
- if (entryIds.length === 0) return result;
8974
- const rows = await (await getDb()).selectFrom("content_taxonomies").innerJoin("taxonomies", "taxonomies.id", "content_taxonomies.taxonomy_id").select([
8975
- "content_taxonomies.entry_id",
8976
- "taxonomies.id",
8977
- "taxonomies.name",
8978
- "taxonomies.slug",
8979
- "taxonomies.label",
8980
- "taxonomies.parent_id"
8981
- ]).where("content_taxonomies.collection", "=", collection).where("content_taxonomies.entry_id", "in", entryIds).where("taxonomies.name", "=", taxonomyName).execute();
8982
- for (const row of rows) {
8983
- const entryId = row.entry_id;
8984
- const term = {
8985
- id: row.id,
8986
- name: row.name,
8987
- slug: row.slug,
8988
- label: row.label,
8989
- parentId: row.parent_id ?? void 0,
8990
- children: []
8991
- };
8992
- const terms = result.get(entryId);
8993
- if (terms) terms.push(term);
8994
- }
8995
- return result;
8996
- }
8997
- /**
8998
- * Get entries by term (wraps getEmDashCollection)
8999
- */
9000
- async function getEntriesByTerm(collection, taxonomyName, termSlug) {
9001
- const { getEmDashCollection } = await import("./query-B6Vu0d2i.mjs").then((n) => n.o);
9002
- const { entries } = await getEmDashCollection(collection, { where: { [taxonomyName]: termSlug } });
9003
- return entries;
9004
- }
9005
- /**
9006
- * Build tree structure from flat terms
9007
- */
9008
- function buildTree(flatTerms, counts) {
9009
- const map = /* @__PURE__ */ new Map();
9010
- const roots = [];
9011
- for (const term of flatTerms) map.set(term.id, {
9012
- id: term.id,
9013
- name: term.name,
9014
- slug: term.slug,
9015
- label: term.label,
9016
- parentId: term.parent_id ?? void 0,
9017
- description: term.data ? JSON.parse(term.data).description : void 0,
9018
- children: [],
9019
- count: counts.get(term.id) ?? 0
9020
- });
9021
- for (const term of map.values()) if (term.parentId && map.has(term.parentId)) map.get(term.parentId).children.push(term);
9022
- else roots.push(term);
9023
- return roots;
9024
- }
9025
-
9026
8975
  //#endregion
9027
8976
  //#region src/widgets/components.ts
9028
8977
  /**
@@ -9132,18 +9081,53 @@ function getWidgetComponents$1() {
9132
9081
  //#endregion
9133
9082
  //#region src/widgets/index.ts
9134
9083
  /**
9135
- * Get a widget area by name, with all its widgets
9084
+ * Get a widget area by name, with all its widgets.
9085
+ *
9086
+ * Single query with a left join rather than area-then-widgets so the
9087
+ * common case costs one round-trip. An area with no widgets yields one
9088
+ * row with null widget columns, which we skip when mapping.
9136
9089
  */
9137
9090
  async function getWidgetArea(name) {
9138
- const db = await getDb();
9139
- const areaRow = await db.selectFrom("_emdash_widget_areas").selectAll().where("name", "=", name).executeTakeFirst();
9140
- if (!areaRow) return null;
9141
- const widgets = (await db.selectFrom("_emdash_widgets").selectAll().$castTo().where("area_id", "=", areaRow.id).orderBy("sort_order", "asc").execute()).map((row) => rowToWidget(row));
9091
+ const rows = await (await getDb()).selectFrom("_emdash_widget_areas as a").leftJoin("_emdash_widgets as w", "w.area_id", "a.id").select([
9092
+ "a.id as a_id",
9093
+ "a.name as a_name",
9094
+ "a.label as a_label",
9095
+ "a.description as a_description",
9096
+ "w.id as w_id",
9097
+ "w.type as w_type",
9098
+ "w.title as w_title",
9099
+ "w.content as w_content",
9100
+ "w.menu_name as w_menu_name",
9101
+ "w.component_id as w_component_id",
9102
+ "w.component_props as w_component_props",
9103
+ "w.area_id as w_area_id",
9104
+ "w.sort_order as w_sort_order",
9105
+ "w.created_at as w_created_at"
9106
+ ]).where("a.name", "=", name).orderBy("w.sort_order", "asc").execute();
9107
+ const first = rows[0];
9108
+ if (!first) return null;
9109
+ const widgets = [];
9110
+ for (const row of rows) {
9111
+ if (row.w_id === null) continue;
9112
+ const widgetRow = {
9113
+ id: row.w_id,
9114
+ type: row.w_type,
9115
+ title: row.w_title,
9116
+ content: row.w_content,
9117
+ menu_name: row.w_menu_name,
9118
+ component_id: row.w_component_id,
9119
+ component_props: row.w_component_props,
9120
+ area_id: row.w_area_id,
9121
+ sort_order: row.w_sort_order,
9122
+ created_at: row.w_created_at
9123
+ };
9124
+ widgets.push(rowToWidget(widgetRow));
9125
+ }
9142
9126
  return {
9143
- id: areaRow.id,
9144
- name: areaRow.name,
9145
- label: areaRow.label,
9146
- description: areaRow.description ?? void 0,
9127
+ id: first.a_id,
9128
+ name: first.a_name,
9129
+ label: first.a_label,
9130
+ description: first.a_description ?? void 0,
9147
9131
  widgets
9148
9132
  };
9149
9133
  }
@@ -9345,8 +9329,8 @@ async function getSuggestions(db, query, options = {}) {
9345
9329
  validateIdentifier(collection, "collection slug");
9346
9330
  const ftsTable = ftsManager.getFtsTableName(collection);
9347
9331
  const contentTable = ftsManager.getContentTableName(collection);
9348
- const prefixQuery = `${escapeQuery(query)}*`;
9349
- if (!prefixQuery || prefixQuery === "*") continue;
9332
+ const prefixQuery = escapeQuery(query);
9333
+ if (!prefixQuery) continue;
9350
9334
  const results = await sql`
9351
9335
  SELECT
9352
9336
  c.id,
@@ -9506,5 +9490,5 @@ function extractSearchableFields(entry, fields) {
9506
9490
  }
9507
9491
 
9508
9492
  //#endregion
9509
- export { sanitizeHeadersForSandbox as $, getAllSources as A, handleContentGet as At, createNoopSandboxRunner as B, handleContentUnschedule as Bt, isPreviewRequest as C, handleContentCompare as Ct, parseWxrDate as D, handleContentDelete as Dt, wordpressRestSource as E, handleContentCreate as Et, registerSource as F, handleContentPublish as Ft, DEV_CONSOLE_EMAIL_PLUGIN_ID as G, image as Gt, createPluginManager as H, validateRev as Ht, importReusableBlocksAsSections as I, handleContentRestore as It, HookPipeline as J, devConsoleEmailDeliver as K, isStandardPluginDefinition as L, handleContentSchedule as Lt, getSource as M, handleContentList as Mt, getUrlSources as N, handleContentListTrashed as Nt, wxrSource as O, handleContentDiscardDraft as Ot, probeUrl as P, handleContentPermanentDelete as Pt, extractRequestMeta as Q, NoopSandboxRunner as R, handleContentTranslations as Rt, getPreviewToken as S, hashString as St, getPreviewUrl as T, handleContentCountTrashed as Tt, PluginRouteError as U, portableText as Ut, PluginManager as V, handleContentUpdate as Vt, PluginRouteRegistry as W, reference as Wt, resolveExclusiveHooks as X, createHookPipeline as Y, CronExecutor as Z, getTermsForEntries as _, handleRevisionGet as _t, search as a, isSafeHref as at, getCommentCount as b, generateManifest as bt, getWidgetArea as c, getSection as ct, getEntriesByTerm as d, getCollectionInfo as dt, definePlugin as et, getEntryTerms as f, handleMediaCreate as ft, getTerm as g, handleMediaUpdate as gt, getTaxonomyTerms as h, handleMediaList as ht, getSuggestions as i, prosemirrorToPortableText as it, getFileSources as j, handleContentGetIncludingTrashed as jt, clearSources as k, handleContentDuplicate as kt, getWidgetAreas as l, getSections as lt, getTaxonomyDefs as m, handleMediaGet as mt, extractSearchableFields as n, parseWxrString as nt, searchCollection as o, sanitizeHref as ot, getTaxonomyDef as p, handleMediaDelete as pt, EmailPipeline as q, getSearchStats as r, portableTextToProsemirror as rt, searchWithDb as s, loadBundleFromR2 as st, extractPlainText as t, parseWxr as tt, getWidgetComponents as u, PluginStateRepository as ut, getMenu as v, handleRevisionList as vt, buildPreviewUrl as w, handleContentCountScheduled as wt, getComments as x, computeContentHash as xt, getMenus as y, handleRevisionRestore as yt, SandboxNotAvailableError as z, handleContentUnpublish as zt };
9510
- //# sourceMappingURL=search-Cn1SYvYF.mjs.map
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