emdash 0.8.0 → 0.10.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 (317) hide show
  1. package/dist/{adapters-BKSf3T9R.d.mts → adapters-BktHA7EO.d.mts} +1 -1
  2. package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
  3. package/dist/{apply-x0eMK1lX.mjs → apply-UsrFuO7l.mjs} +207 -355
  4. package/dist/apply-UsrFuO7l.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 +118 -4
  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 +14 -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 +15 -10
  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 +8 -5
  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 +70 -121
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/types.d.mts +25 -10
  24. package/dist/astro/types.d.mts.map +1 -1
  25. package/dist/{byline-Chbr2GoP.mjs → byline-C3vnhIpU.mjs} +4 -4
  26. package/dist/{byline-Chbr2GoP.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
  27. package/dist/bylines-esI7ioa9.mjs +113 -0
  28. package/dist/bylines-esI7ioa9.mjs.map +1 -0
  29. package/dist/cache-fTzxgMFJ.mjs +65 -0
  30. package/dist/cache-fTzxgMFJ.mjs.map +1 -0
  31. package/dist/{chunks-HGz06Soa.mjs → chunks-Da2-b-oA.mjs} +8 -2
  32. package/dist/{chunks-HGz06Soa.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
  33. package/dist/cli/index.mjs +456 -90
  34. package/dist/cli/index.mjs.map +1 -1
  35. package/dist/client/cf-access.d.mts +1 -1
  36. package/dist/client/index.d.mts +1 -1
  37. package/dist/client/index.mjs +3 -3
  38. package/dist/client/index.mjs.map +1 -1
  39. package/dist/{config-BXwuX8Bx.mjs → config-CVssduLe.mjs} +1 -1
  40. package/dist/{config-BXwuX8Bx.mjs.map → config-CVssduLe.mjs.map} +1 -1
  41. package/dist/{content-BcQPYxdV.mjs → content-C7G4QXkK.mjs} +42 -14
  42. package/dist/content-C7G4QXkK.mjs.map +1 -0
  43. package/dist/db/index.d.mts +3 -3
  44. package/dist/db/index.mjs +2 -2
  45. package/dist/db/libsql.d.mts +1 -1
  46. package/dist/db/libsql.d.mts.map +1 -1
  47. package/dist/db/libsql.mjs +7 -2
  48. package/dist/db/libsql.mjs.map +1 -1
  49. package/dist/db/postgres.d.mts +1 -1
  50. package/dist/db/sqlite.d.mts +1 -1
  51. package/dist/db/sqlite.d.mts.map +1 -1
  52. package/dist/db/sqlite.mjs +8 -3
  53. package/dist/db/sqlite.mjs.map +1 -1
  54. package/dist/{db-errors-l1Qh2RPR.mjs → db-errors-B7P2pSCn.mjs} +1 -1
  55. package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
  56. package/dist/{default-DCVqE5ib.mjs → default-pHuz9WF6.mjs} +1 -1
  57. package/dist/{default-DCVqE5ib.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
  58. package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
  59. package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
  60. package/dist/{error-zG5T1UGA.mjs → error-DqnRMM5z.mjs} +1 -1
  61. package/dist/{error-zG5T1UGA.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
  62. package/dist/{index-DIb-CzNx.d.mts → index-DjPMOfO0.d.mts} +162 -87
  63. package/dist/index-DjPMOfO0.d.mts.map +1 -0
  64. package/dist/index.d.mts +11 -11
  65. package/dist/index.mjs +27 -24
  66. package/dist/{load-CyEoextb.mjs → load-sXRuM7Us.mjs} +2 -2
  67. package/dist/{load-CyEoextb.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
  68. package/dist/{loader-CndGj8kM.mjs → loader-Bx2_9-5e.mjs} +53 -8
  69. package/dist/loader-Bx2_9-5e.mjs.map +1 -0
  70. package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-CXAbd1vH.mjs} +33 -3
  71. package/dist/manifest-schema-CXAbd1vH.mjs.map +1 -0
  72. package/dist/media/index.d.mts +1 -1
  73. package/dist/media/index.mjs +1 -1
  74. package/dist/media/local-runtime.d.mts +7 -7
  75. package/dist/{mode-BnAOqItE.mjs → mode-YhqNVef_.mjs} +1 -1
  76. package/dist/{mode-BnAOqItE.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
  77. package/dist/options-nPxWnrya.mjs +117 -0
  78. package/dist/options-nPxWnrya.mjs.map +1 -0
  79. package/dist/page/index.d.mts +2 -2
  80. package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
  81. package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
  82. package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
  83. package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
  84. package/dist/{placeholder-C-fk5hYI.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
  85. package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
  86. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  87. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  88. package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
  89. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  90. package/dist/public-url-B1AxbbbQ.mjs +51 -0
  91. package/dist/public-url-B1AxbbbQ.mjs.map +1 -0
  92. package/dist/{query-fqEdLFms.mjs → query-Bo-msrmu.mjs} +114 -16
  93. package/dist/query-Bo-msrmu.mjs.map +1 -0
  94. package/dist/{redirect-D_pshWdf.mjs → redirect-C5H7VGIX.mjs} +11 -6
  95. package/dist/redirect-C5H7VGIX.mjs.map +1 -0
  96. package/dist/{registry-C3Mr0ODu.mjs → registry-Beb7wxFc.mjs} +39 -5
  97. package/dist/registry-Beb7wxFc.mjs.map +1 -0
  98. package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-C-tIpYIw.mjs} +1 -1
  99. package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
  100. package/dist/runner-Clwe4Mme.d.mts +44 -0
  101. package/dist/runner-Clwe4Mme.d.mts.map +1 -0
  102. package/dist/{runner-tQ7BJ4T7.mjs → runner-DMnlIkh4.mjs} +616 -191
  103. package/dist/runner-DMnlIkh4.mjs.map +1 -0
  104. package/dist/runtime.d.mts +6 -6
  105. package/dist/runtime.mjs +2 -2
  106. package/dist/{search-BoZYFuUk.mjs → search-DkN-BqsS.mjs} +270 -152
  107. package/dist/search-DkN-BqsS.mjs.map +1 -0
  108. package/dist/secrets-CZ8rxLX3.mjs +314 -0
  109. package/dist/secrets-CZ8rxLX3.mjs.map +1 -0
  110. package/dist/seed/index.d.mts +2 -2
  111. package/dist/seed/index.mjs +13 -11
  112. package/dist/seo/index.d.mts +1 -1
  113. package/dist/storage/local.d.mts +1 -1
  114. package/dist/storage/local.mjs +1 -1
  115. package/dist/storage/s3.d.mts +1 -1
  116. package/dist/storage/s3.mjs +1 -1
  117. package/dist/taxonomies-CTtewrSQ.mjs +407 -0
  118. package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
  119. package/dist/taxonomy-DSxx2K2L.mjs +218 -0
  120. package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
  121. package/dist/{tokens-D9vnZqYS.mjs → tokens-CyRDPVW2.mjs} +1 -1
  122. package/dist/{tokens-D9vnZqYS.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
  123. package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
  124. package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
  125. package/dist/{transport-CUnEL3Vs.d.mts → transport-DX_5rpsq.d.mts} +1 -1
  126. package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
  127. package/dist/{transport-C9ugt2Nr.mjs → transport-xpzIjCIB.mjs} +6 -5
  128. package/dist/{transport-C9ugt2Nr.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
  129. package/dist/{types-BrA0xf5I.d.mts → types-B_CXXnzh.d.mts} +1 -1
  130. package/dist/{types-BrA0xf5I.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
  131. package/dist/{types-DIMwPFub.d.mts → types-C-aFbqmA.d.mts} +1 -1
  132. package/dist/{types-DIMwPFub.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
  133. package/dist/types-CoO6mpV3.mjs +68 -0
  134. package/dist/types-CoO6mpV3.mjs.map +1 -0
  135. package/dist/{types-i36XcA_X.d.mts → types-D19uBYWn.d.mts} +83 -7
  136. package/dist/types-D19uBYWn.d.mts.map +1 -0
  137. package/dist/{types-BmPPSUEx.d.mts → types-Dl1fgFjn.d.mts} +24 -2
  138. package/dist/{types-BmPPSUEx.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
  139. package/dist/{types-CS8FIX7L.d.mts → types-Dtx1mSMX.d.mts} +9 -1
  140. package/dist/types-Dtx1mSMX.d.mts.map +1 -0
  141. package/dist/{types-Bm1dn-q3.mjs → types-Eg829jj9.mjs} +1 -1
  142. package/dist/{types-Bm1dn-q3.mjs.map → types-Eg829jj9.mjs.map} +1 -1
  143. package/dist/{types-CgqmmMJB.mjs → types-K-EkEQCI.mjs} +1 -1
  144. package/dist/{types-CgqmmMJB.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
  145. package/dist/{validate-CxVsLehf.mjs → validate-CBIbxM3L.mjs} +14 -10
  146. package/dist/validate-CBIbxM3L.mjs.map +1 -0
  147. package/dist/{validate-DHxmpFJt.d.mts → validate-DHGwADqO.d.mts} +18 -5
  148. package/dist/validate-DHGwADqO.d.mts.map +1 -0
  149. package/dist/{validation-C-ZpN2GI.mjs → validation-B1NYiEos.mjs} +6 -6
  150. package/dist/{validation-C-ZpN2GI.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
  151. package/dist/version-CMD42IRC.mjs +7 -0
  152. package/dist/{version-Bbq8TCrz.mjs.map → version-CMD42IRC.mjs.map} +1 -1
  153. package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-BNJDQBSZ.mjs} +11 -6
  154. package/dist/{zod-generator-CpwccCIv.mjs.map → zod-generator-BNJDQBSZ.mjs.map} +1 -1
  155. package/locals.d.ts +1 -6
  156. package/package.json +9 -8
  157. package/src/api/handlers/comments.ts +6 -4
  158. package/src/api/handlers/content.ts +40 -1
  159. package/src/api/handlers/dashboard.ts +29 -36
  160. package/src/api/handlers/device-flow.ts +5 -0
  161. package/src/api/handlers/marketplace.ts +11 -4
  162. package/src/api/handlers/menus.ts +256 -75
  163. package/src/api/handlers/oauth-authorization.ts +72 -33
  164. package/src/api/handlers/revision.ts +23 -14
  165. package/src/api/handlers/taxonomies.ts +273 -100
  166. package/src/api/public-url.ts +48 -2
  167. package/src/api/schemas/comments.ts +2 -2
  168. package/src/api/schemas/common.ts +7 -0
  169. package/src/api/schemas/content.ts +17 -0
  170. package/src/api/schemas/menus.ts +23 -0
  171. package/src/api/schemas/sections.ts +3 -3
  172. package/src/api/schemas/taxonomies.ts +39 -0
  173. package/src/api/schemas/users.ts +1 -1
  174. package/src/api/types.ts +5 -1
  175. package/src/astro/integration/index.ts +17 -0
  176. package/src/astro/integration/routes.ts +10 -0
  177. package/src/astro/integration/runtime.ts +30 -0
  178. package/src/astro/integration/virtual-modules.ts +32 -2
  179. package/src/astro/integration/vite-config.ts +6 -1
  180. package/src/astro/middleware/auth.ts +13 -6
  181. package/src/astro/middleware/redirect.ts +29 -16
  182. package/src/astro/middleware/request-context.ts +15 -5
  183. package/src/astro/middleware.ts +23 -9
  184. package/src/astro/routes/api/auth/invite/complete.ts +6 -1
  185. package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
  186. package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
  187. package/src/astro/routes/api/auth/signup/complete.ts +6 -1
  188. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
  189. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  190. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
  191. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
  192. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
  193. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
  194. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
  195. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
  196. package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
  197. package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
  198. package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
  199. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
  200. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
  201. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
  202. package/src/astro/routes/api/manifest.ts +62 -45
  203. package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
  204. package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
  205. package/src/astro/routes/api/menus/[name]/items.ts +16 -6
  206. package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
  207. package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
  208. package/src/astro/routes/api/menus/[name].ts +19 -10
  209. package/src/astro/routes/api/menus/index.ts +9 -6
  210. package/src/astro/routes/api/openapi.json.ts +27 -10
  211. package/src/astro/routes/api/redirects/404s/index.ts +10 -4
  212. package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
  213. package/src/astro/routes/api/redirects/[id].ts +10 -4
  214. package/src/astro/routes/api/redirects/index.ts +7 -3
  215. package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
  216. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
  217. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
  218. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
  219. package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
  220. package/src/astro/routes/api/schema/collections/index.ts +1 -1
  221. package/src/astro/routes/api/search/index.ts +10 -2
  222. package/src/astro/routes/api/sections/[slug].ts +10 -4
  223. package/src/astro/routes/api/sections/index.ts +7 -3
  224. package/src/astro/routes/api/setup/admin-verify.ts +6 -1
  225. package/src/astro/routes/api/snapshot.ts +44 -18
  226. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
  227. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
  228. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
  229. package/src/astro/routes/api/taxonomies/index.ts +9 -7
  230. package/src/astro/routes/api/themes/preview.ts +11 -5
  231. package/src/astro/types.ts +23 -3
  232. package/src/auth/allowed-origins.ts +168 -0
  233. package/src/auth/passkey-config.ts +35 -13
  234. package/src/bylines/index.ts +37 -88
  235. package/src/cli/commands/auth.ts +28 -6
  236. package/src/cli/commands/bundle-utils.ts +11 -2
  237. package/src/cli/commands/bundle.ts +28 -8
  238. package/src/cli/commands/content.ts +13 -0
  239. package/src/cli/commands/export-seed.ts +82 -21
  240. package/src/cli/commands/login.ts +8 -1
  241. package/src/cli/commands/plugin-init.ts +216 -90
  242. package/src/cli/commands/publish.ts +24 -0
  243. package/src/cli/commands/secrets.ts +183 -0
  244. package/src/cli/credentials.ts +1 -1
  245. package/src/cli/index.ts +5 -1
  246. package/src/client/index.ts +4 -4
  247. package/src/client/transport.ts +17 -7
  248. package/src/components/Break.astro +2 -2
  249. package/src/components/EmDashHead.astro +18 -13
  250. package/src/components/Embed.astro +1 -1
  251. package/src/components/Gallery.astro +1 -1
  252. package/src/components/Image.astro +1 -1
  253. package/src/components/InlinePortableTextEditor.tsx +104 -18
  254. package/src/config/secrets.ts +528 -0
  255. package/src/database/dialect-helpers.ts +50 -0
  256. package/src/database/migrations/034_published_at_index.ts +1 -1
  257. package/src/database/migrations/035_bounded_404_log.ts +56 -39
  258. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
  259. package/src/database/migrations/runner.ts +158 -23
  260. package/src/database/repositories/content.ts +47 -12
  261. package/src/database/repositories/redirect.ts +14 -3
  262. package/src/database/repositories/taxonomy.ts +212 -82
  263. package/src/database/types.ts +10 -2
  264. package/src/db/libsql.ts +1 -3
  265. package/src/db/sqlite.ts +2 -5
  266. package/src/emdash-runtime.ts +84 -159
  267. package/src/i18n/resolve.ts +37 -0
  268. package/src/index.ts +9 -0
  269. package/src/loader.ts +73 -3
  270. package/src/mcp/server.ts +180 -54
  271. package/src/menus/index.ts +143 -124
  272. package/src/menus/types.ts +15 -1
  273. package/src/page/site-identity.ts +58 -0
  274. package/src/plugins/adapt-sandbox-entry.ts +22 -10
  275. package/src/plugins/context.ts +13 -10
  276. package/src/plugins/define-plugin.ts +40 -12
  277. package/src/plugins/hooks.ts +23 -19
  278. package/src/plugins/index.ts +9 -0
  279. package/src/plugins/manifest-schema.ts +37 -2
  280. package/src/plugins/types.ts +151 -11
  281. package/src/preview/urls.ts +23 -3
  282. package/src/query.ts +148 -5
  283. package/src/redirects/cache.ts +38 -18
  284. package/src/schema/registry.ts +56 -0
  285. package/src/schema/zod-generator.ts +39 -7
  286. package/src/seed/apply.ts +142 -54
  287. package/src/seed/types.ts +14 -1
  288. package/src/seed/validate.ts +27 -13
  289. package/src/settings/index.ts +80 -6
  290. package/src/settings/types.ts +23 -1
  291. package/src/taxonomies/index.ts +237 -210
  292. package/src/taxonomies/types.ts +10 -0
  293. package/dist/apply-x0eMK1lX.mjs.map +0 -1
  294. package/dist/bylines-CRNsVG88.mjs +0 -157
  295. package/dist/bylines-CRNsVG88.mjs.map +0 -1
  296. package/dist/cache-BkKBuIvS.mjs +0 -56
  297. package/dist/cache-BkKBuIvS.mjs.map +0 -1
  298. package/dist/chunk-ClPoSABd.mjs +0 -21
  299. package/dist/content-BcQPYxdV.mjs.map +0 -1
  300. package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
  301. package/dist/index-DIb-CzNx.d.mts.map +0 -1
  302. package/dist/loader-CndGj8kM.mjs.map +0 -1
  303. package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
  304. package/dist/query-fqEdLFms.mjs.map +0 -1
  305. package/dist/redirect-D_pshWdf.mjs.map +0 -1
  306. package/dist/registry-C3Mr0ODu.mjs.map +0 -1
  307. package/dist/runner-OURCaApa.d.mts +0 -34
  308. package/dist/runner-OURCaApa.d.mts.map +0 -1
  309. package/dist/runner-tQ7BJ4T7.mjs.map +0 -1
  310. package/dist/search-BoZYFuUk.mjs.map +0 -1
  311. package/dist/taxonomies-B4IAshV8.mjs +0 -308
  312. package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
  313. package/dist/types-CS8FIX7L.d.mts.map +0 -1
  314. package/dist/types-i36XcA_X.d.mts.map +0 -1
  315. package/dist/validate-CxVsLehf.mjs.map +0 -1
  316. package/dist/validate-DHxmpFJt.d.mts.map +0 -1
  317. package/dist/version-Bbq8TCrz.mjs +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"db-errors-l1Qh2RPR.mjs","names":[],"sources":["../src/utils/db-errors.ts"],"sourcesContent":["/**\n * Shared detection helpers for database-layer error messages.\n *\n * Different SQL dialects phrase \"table or relation does not exist\" differently:\n *\n * - SQLite / D1: \"no such table: foo\"\n * - PostgreSQL: 'relation \"foo\" does not exist'\n * 'table \"foo\" does not exist'\n * - MySQL (future): \"Table 'db.foo' doesn't exist\"\n *\n * Runtime code paths that short-circuit on missing tables (pre-migration\n * probes, optional feature tables, etc.) should use these helpers rather\n * than hand-rolling string matches per call-site.\n */\n\n/**\n * Extract a lowercase error message from any unknown value, safely.\n */\nfunction messageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message.toLowerCase();\n\tif (typeof error === \"string\") return error.toLowerCase();\n\treturn \"\";\n}\n\n/**\n * Returns true when `error` is a \"table does not exist\" error across the\n * dialects EmDash supports (D1/SQLite and PostgreSQL). Used by runtime\n * probes to treat pre-migration databases as empty without logging a scary\n * warning, while still propagating unrelated errors (permissions, connection\n * loss, syntax issues) to callers.\n */\nexport function isMissingTableError(error: unknown): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\n\t// SQLite / D1\n\tif (message.includes(\"no such table\")) return true;\n\n\t// PostgreSQL (and some MySQL variants): \"relation ... does not exist\" /\n\t// \"table ... does not exist\" / \"doesn't exist\".\n\tif (message.includes(\"does not exist\") || message.includes(\"doesn't exist\")) {\n\t\treturn message.includes(\"relation\") || message.includes(\"table\");\n\t}\n\n\treturn false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAS,UAAU,OAAwB;AAC1C,KAAI,iBAAiB,MAAO,QAAO,MAAM,QAAQ,aAAa;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,aAAa;AACzD,QAAO;;;;;;;;;AAUR,SAAgB,oBAAoB,OAAyB;CAC5D,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,QAAQ,SAAS,gBAAgB,CAAE,QAAO;AAI9C,KAAI,QAAQ,SAAS,iBAAiB,IAAI,QAAQ,SAAS,gBAAgB,CAC1E,QAAO,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,QAAQ;AAGjE,QAAO"}
1
+ {"version":3,"file":"db-errors-B7P2pSCn.mjs","names":[],"sources":["../src/utils/db-errors.ts"],"sourcesContent":["/**\n * Shared detection helpers for database-layer error messages.\n *\n * Different SQL dialects phrase \"table or relation does not exist\" differently:\n *\n * - SQLite / D1: \"no such table: foo\"\n * - PostgreSQL: 'relation \"foo\" does not exist'\n * 'table \"foo\" does not exist'\n * - MySQL (future): \"Table 'db.foo' doesn't exist\"\n *\n * Runtime code paths that short-circuit on missing tables (pre-migration\n * probes, optional feature tables, etc.) should use these helpers rather\n * than hand-rolling string matches per call-site.\n */\n\n/**\n * Extract a lowercase error message from any unknown value, safely.\n */\nfunction messageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message.toLowerCase();\n\tif (typeof error === \"string\") return error.toLowerCase();\n\treturn \"\";\n}\n\n/**\n * Returns true when `error` is a \"table does not exist\" error across the\n * dialects EmDash supports (D1/SQLite and PostgreSQL). Used by runtime\n * probes to treat pre-migration databases as empty without logging a scary\n * warning, while still propagating unrelated errors (permissions, connection\n * loss, syntax issues) to callers.\n */\nexport function isMissingTableError(error: unknown): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\n\t// SQLite / D1\n\tif (message.includes(\"no such table\")) return true;\n\n\t// PostgreSQL (and some MySQL variants): \"relation ... does not exist\" /\n\t// \"table ... does not exist\" / \"doesn't exist\".\n\tif (message.includes(\"does not exist\") || message.includes(\"doesn't exist\")) {\n\t\treturn message.includes(\"relation\") || message.includes(\"table\");\n\t}\n\n\treturn false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAS,UAAU,OAAwB;AAC1C,KAAI,iBAAiB,MAAO,QAAO,MAAM,QAAQ,aAAa;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,aAAa;AACzD,QAAO;;;;;;;;;AAUR,SAAgB,oBAAoB,OAAyB;CAC5D,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,QAAQ,SAAS,gBAAgB,CAAE,QAAO;AAI9C,KAAI,QAAQ,SAAS,iBAAiB,IAAI,QAAQ,SAAS,gBAAgB,CAC1E,QAAO,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,QAAQ;AAGjE,QAAO"}
@@ -78,4 +78,4 @@ const defaultSeed = {
78
78
 
79
79
  //#endregion
80
80
  export { defaultSeed as t };
81
- //# sourceMappingURL=default-DCVqE5ib.mjs.map
81
+ //# sourceMappingURL=default-pHuz9WF6.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-DCVqE5ib.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
1
+ {"version":3,"file":"default-pHuz9WF6.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
@@ -53,6 +53,23 @@ async function tableExists(db, tableName) {
53
53
  `.execute(db)).rows.length > 0;
54
54
  }
55
55
  /**
56
+ * Check if a column exists in the database.
57
+ */
58
+ async function columnExists(db, tableName, columnName) {
59
+ if (isPostgres(db)) return (await sql`
60
+ SELECT EXISTS(
61
+ SELECT 1 FROM information_schema.columns
62
+ WHERE table_schema = current_schema()
63
+ AND table_name = ${tableName}
64
+ AND column_name = ${columnName}
65
+ ) as exists
66
+ `.execute(db)).rows[0]?.exists === true;
67
+ return (await sql`
68
+ SELECT name FROM pragma_table_info(${tableName})
69
+ WHERE name = ${columnName}
70
+ `.execute(db)).rows.length > 0;
71
+ }
72
+ /**
56
73
  * List tables matching a LIKE pattern.
57
74
  */
58
75
  async function listTablesLike(db, pattern) {
@@ -89,5 +106,5 @@ function jsonExtractExpr(db, column, path) {
89
106
  }
90
107
 
91
108
  //#endregion
92
- export { isSqlite as a, tableExists as c, isPostgres as i, currentTimestamp as n, jsonExtractExpr as o, currentTimestampValue as r, listTablesLike as s, binaryType as t };
93
- //# sourceMappingURL=dialect-helpers-DhTzaUxP.mjs.map
109
+ export { isPostgres as a, listTablesLike as c, currentTimestampValue as i, tableExists as l, columnExists as n, isSqlite as o, currentTimestamp as r, jsonExtractExpr as s, binaryType as t };
110
+ //# sourceMappingURL=dialect-helpers-BKCvISIQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialect-helpers-BKCvISIQ.mjs","names":[],"sources":["../src/database/dialect-helpers.ts"],"sourcesContent":["/**\n * Dialect-specific SQL helpers\n *\n * Every function takes a Kysely `db` instance and detects the dialect from\n * the adapter class. No module-level state, no globals, no heuristics —\n * the adapter is the source of truth.\n *\n * This is NOT an ORM abstraction — just targeted helpers for the ~15 places\n * that use raw dialect-specific SQL. Most Kysely schema builder code already\n * works cross-dialect.\n */\n\nimport type { ColumnDataType, Kysely, RawBuilder } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport type { DatabaseDialectType } from \"../db/adapters.js\";\nimport { validateIdentifier, validateJsonFieldName } from \"./validate.js\";\n\nexport type { DatabaseDialectType };\n\n/**\n * Detect dialect type from a Kysely instance via the adapter class name.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function detectDialect(db: Kysely<any>): DatabaseDialectType {\n\tconst name = db.getExecutor().adapter.constructor.name;\n\tif (name === \"PostgresAdapter\") return \"postgres\";\n\treturn \"sqlite\";\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function isSqlite(db: Kysely<any>): boolean {\n\treturn detectDialect(db) === \"sqlite\";\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function isPostgres(db: Kysely<any>): boolean {\n\treturn detectDialect(db) === \"postgres\";\n}\n\n/**\n * Default timestamp expression for column defaults.\n * Wrapped in parens for use in CREATE TABLE ... DEFAULT (...).\n *\n * sqlite: (datetime('now'))\n * postgres: CURRENT_TIMESTAMP\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function currentTimestamp(db: Kysely<any>): RawBuilder<string> {\n\tif (isPostgres(db)) {\n\t\treturn sql`CURRENT_TIMESTAMP`;\n\t}\n\treturn sql`(datetime('now'))`;\n}\n\n/**\n * Timestamp expression for use in WHERE clauses and SET expressions.\n * No wrapping parens.\n *\n * sqlite: datetime('now')\n * postgres: CURRENT_TIMESTAMP\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function currentTimestampValue(db: Kysely<any>): RawBuilder<string> {\n\tif (isPostgres(db)) {\n\t\treturn sql`CURRENT_TIMESTAMP`;\n\t}\n\treturn sql`datetime('now')`;\n}\n\n/**\n * Check if a table exists in the database.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function tableExists(db: Kysely<any>, tableName: string): Promise<boolean> {\n\tif (isPostgres(db)) {\n\t\tconst result = await sql<{ exists: boolean }>`\n\t\t\tSELECT EXISTS(\n\t\t\t\tSELECT 1 FROM information_schema.tables\n\t\t\t\tWHERE table_schema = 'public' AND table_name = ${tableName}\n\t\t\t) as exists\n\t\t`.execute(db);\n\t\treturn result.rows[0]?.exists === true;\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'table' AND name = ${tableName}\n\t`.execute(db);\n\treturn result.rows.length > 0;\n}\n\n/**\n * Check if an index exists in the database.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function indexExists(db: Kysely<any>, indexName: string): Promise<boolean> {\n\tif (isPostgres(db)) {\n\t\tconst result = await sql<{ exists: boolean }>`\n\t\t\tSELECT EXISTS(\n\t\t\t\tSELECT 1 FROM pg_indexes\n\t\t\t\tWHERE schemaname = current_schema() AND indexname = ${indexName}\n\t\t\t) as exists\n\t\t`.execute(db);\n\t\treturn result.rows[0]?.exists === true;\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'index' AND name = ${indexName}\n\t`.execute(db);\n\treturn result.rows.length > 0;\n}\n\n/**\n * Check if a column exists in the database.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function columnExists(\n\tdb: Kysely<any>,\n\ttableName: string,\n\tcolumnName: string,\n): Promise<boolean> {\n\tif (isPostgres(db)) {\n\t\tconst result = await sql<{ exists: boolean }>`\n\t\t\tSELECT EXISTS(\n\t\t\t\tSELECT 1 FROM information_schema.columns\n\t\t\t\tWHERE table_schema = current_schema()\n\t\t\t\t\tAND table_name = ${tableName}\n\t\t\t\t\tAND column_name = ${columnName}\n\t\t\t) as exists\n\t\t`.execute(db);\n\t\treturn result.rows[0]?.exists === true;\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM pragma_table_info(${tableName})\n\t\tWHERE name = ${columnName}\n\t`.execute(db);\n\treturn result.rows.length > 0;\n}\n\n/**\n * List tables matching a LIKE pattern.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport async function listTablesLike(db: Kysely<any>, pattern: string): Promise<string[]> {\n\tif (isPostgres(db)) {\n\t\tconst result = await sql<{ table_name: string }>`\n\t\t\tSELECT table_name FROM information_schema.tables\n\t\t\tWHERE table_schema = 'public' AND table_name LIKE ${pattern}\n\t\t`.execute(db);\n\t\treturn result.rows.map((r) => r.table_name);\n\t}\n\n\tconst result = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'table' AND name LIKE ${pattern}\n\t`.execute(db);\n\treturn result.rows.map((r) => r.name);\n}\n\n/**\n * Column type for binary data.\n *\n * sqlite: blob\n * postgres: bytea\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function binaryType(db: Kysely<any>): ColumnDataType {\n\tif (isPostgres(db)) {\n\t\treturn \"bytea\";\n\t}\n\treturn \"blob\";\n}\n\n/**\n * SQL expression for extracting a field from a JSON/JSONB column.\n *\n * sqlite: json_extract(column, '$.path')\n * postgres: column->>'path'\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function jsonExtractExpr(db: Kysely<any>, column: string, path: string): string {\n\tvalidateIdentifier(column, \"JSON column name\");\n\tvalidateJsonFieldName(path, \"JSON path\");\n\tif (isPostgres(db)) {\n\t\treturn `${column}->>'${path}'`;\n\t}\n\treturn `json_extract(${column}, '$.${path}')`;\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,cAAc,IAAsC;AAEnE,KADa,GAAG,aAAa,CAAC,QAAQ,YAAY,SACrC,kBAAmB,QAAO;AACvC,QAAO;;AAIR,SAAgB,SAAS,IAA0B;AAClD,QAAO,cAAc,GAAG,KAAK;;AAI9B,SAAgB,WAAW,IAA0B;AACpD,QAAO,cAAc,GAAG,KAAK;;;;;;;;;AAW9B,SAAgB,iBAAiB,IAAqC;AACrE,KAAI,WAAW,GAAG,CACjB,QAAO,GAAG;AAEX,QAAO,GAAG;;;;;;;;;AAWX,SAAgB,sBAAsB,IAAqC;AAC1E,KAAI,WAAW,GAAG,CACjB,QAAO,GAAG;AAEX,QAAO,GAAG;;;;;AAOX,eAAsB,YAAY,IAAiB,WAAqC;AACvF,KAAI,WAAW,GAAG,CAOjB,SANe,MAAM,GAAwB;;;qDAGM,UAAU;;IAE3D,QAAQ,GAAG,EACC,KAAK,IAAI,WAAW;AAOnC,SAJe,MAAM,GAAqB;;oCAEP,UAAU;GAC3C,QAAQ,GAAG,EACC,KAAK,SAAS;;;;;AA6B7B,eAAsB,aACrB,IACA,WACA,YACmB;AACnB,KAAI,WAAW,GAAG,CASjB,SARe,MAAM,GAAwB;;;;wBAIvB,UAAU;yBACT,WAAW;;IAEhC,QAAQ,GAAG,EACC,KAAK,IAAI,WAAW;AAOnC,SAJe,MAAM,GAAqB;uCACJ,UAAU;iBAChC,WAAW;GACzB,QAAQ,GAAG,EACC,KAAK,SAAS;;;;;AAO7B,eAAsB,eAAe,IAAiB,SAAoC;AACzF,KAAI,WAAW,GAAG,CAKjB,SAJe,MAAM,GAA2B;;uDAEK,QAAQ;IAC3D,QAAQ,GAAG,EACC,KAAK,KAAK,MAAM,EAAE,WAAW;AAO5C,SAJe,MAAM,GAAqB;;uCAEJ,QAAQ;GAC5C,QAAQ,GAAG,EACC,KAAK,KAAK,MAAM,EAAE,KAAK;;;;;;;;AAUtC,SAAgB,WAAW,IAAiC;AAC3D,KAAI,WAAW,GAAG,CACjB,QAAO;AAER,QAAO;;;;;;;;AAUR,SAAgB,gBAAgB,IAAiB,QAAgB,MAAsB;AACtF,oBAAmB,QAAQ,mBAAmB;AAC9C,uBAAsB,MAAM,YAAY;AACxC,KAAI,WAAW,GAAG,CACjB,QAAO,GAAG,OAAO,MAAM,KAAK;AAE7B,QAAO,gBAAgB,OAAO,OAAO,KAAK"}
@@ -24,4 +24,4 @@ function apiError(code, message, status) {
24
24
 
25
25
  //#endregion
26
26
  export { apiError as t };
27
- //# sourceMappingURL=error-zG5T1UGA.mjs.map
27
+ //# sourceMappingURL=error-DqnRMM5z.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-zG5T1UGA.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { InvalidCursorError } from \"../database/repositories/types.js\";\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ error: { code, message } }` with correct Content-Type.\n * Use this for all error responses in API routes.\n */\nexport function apiError(code: string, message: string, status: number): Response {\n\treturn Response.json({ error: { code, message } }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\t// Bubble malformed-cursor errors as a structured 400 instead of a\n\t// generic 500.\n\tif (error instanceof InvalidCursorError) {\n\t\treturn apiError(\"INVALID_CURSOR\", error.message, 400);\n\t}\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(result.error.code, result.error.message, mapErrorStatus(result.error.code));\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;AAoBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SAAS,MAAc,SAAiB,QAA0B;AACjF,QAAO,SAAS,KAAK,EAAE,OAAO;EAAE;EAAM;EAAS,EAAE,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC"}
1
+ {"version":3,"file":"error-DqnRMM5z.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { InvalidCursorError } from \"../database/repositories/types.js\";\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ error: { code, message } }` with correct Content-Type.\n * Use this for all error responses in API routes.\n */\nexport function apiError(code: string, message: string, status: number): Response {\n\treturn Response.json({ error: { code, message } }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\t// Bubble malformed-cursor errors as a structured 400 instead of a\n\t// generic 500.\n\tif (error instanceof InvalidCursorError) {\n\t\treturn apiError(\"INVALID_CURSOR\", error.message, 400);\n\t}\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(result.error.code, result.error.message, mapErrorStatus(result.error.code));\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;AAoBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SAAS,MAAc,SAAiB,QAA0B;AACjF,QAAO,SAAS,KAAK,EAAE,OAAO;EAAE;EAAM;EAAS,EAAE,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC"}
@@ -1,10 +1,10 @@
1
- import { _ as MediaValue, m as MediaProviderDescriptor } from "./placeholder-D29tWZ7o.mjs";
2
- import { t as Database } from "./types-CS8FIX7L.mjs";
3
- import { a as ContentSeoInput, c as FindManyOptions, d as UpdateContentInput, l as FindManyResult, o as CreateContentInput, r as ContentItem, t as BylineSummary } from "./types-BrA0xf5I.mjs";
4
- import { A as PageMetadataContribution, B as PluginDefinition, D as PageFragmentContribution, H as PluginManifest, J as RequestMeta, O as PageFragmentEvent, W as PluginStorageConfig, X as ResolvedPlugin, a as CommentAfterModerateEvent, g as EmailMessage, h as CronEvent, j as PageMetadataEvent, r as CommentAfterCreateEvent, s as CommentBeforeCreateEvent, tt as StandardPluginDefinition, w as MediaItem$1 } from "./types-i36XcA_X.mjs";
5
- import { a as Collection, b as UpdateFieldInput, c as CollectionWithFields, d as CreateFieldInput, i as SiteSettings, p as Field, r as SiteSettingKey, u as CreateCollectionInput, y as UpdateCollectionInput } from "./types-BmPPSUEx.mjs";
6
- import { d as Storage } from "./types-DIMwPFub.mjs";
7
- import { t as DatabaseDescriptor } from "./adapters-BKSf3T9R.mjs";
1
+ import { _ as MediaValue, m as MediaProviderDescriptor } from "./placeholder-CDPtkelt.mjs";
2
+ import { t as Database } from "./types-Dtx1mSMX.mjs";
3
+ import { a as ContentSeoInput, c as FindManyOptions, d as UpdateContentInput, l as FindManyResult, o as CreateContentInput, r as ContentItem, t as BylineSummary } from "./types-B_CXXnzh.mjs";
4
+ import { $ as ResolvedPlugin, A as PageFragmentContribution, D as MediaItem$1, G as PluginManifest, N as PageMetadataContribution, P as PageMetadataEvent, U as PluginDefinition, Z as RequestMeta, c as CommentBeforeCreateEvent, g as CronEvent, i as CommentAfterCreateEvent, it as StandardPluginDefinition, j as PageFragmentEvent, o as CommentAfterModerateEvent, q as PluginStorageConfig, y as EmailMessage } from "./types-D19uBYWn.mjs";
5
+ import { a as Collection, b as UpdateFieldInput, c as CollectionWithFields, d as CreateFieldInput, i as SiteSettings, p as Field, r as SiteSettingKey, u as CreateCollectionInput, y as UpdateCollectionInput } from "./types-Dl1fgFjn.mjs";
6
+ import { d as Storage } from "./types-C-aFbqmA.mjs";
7
+ import { t as DatabaseDescriptor } from "./adapters-BktHA7EO.mjs";
8
8
  import { Kysely } from "kysely";
9
9
  import { z } from "astro/zod";
10
10
  import { z as z$1 } from "zod";
@@ -101,6 +101,16 @@ declare class ContentRepository {
101
101
  /**
102
102
  * Permanently delete content (cannot be undone)
103
103
  */
104
+ /**
105
+ * Permanently delete a soft-deleted content row.
106
+ *
107
+ * Returns `true` only when a soft-deleted (trashed) row was removed.
108
+ * Returns `false` when no row exists OR when the row exists but is live —
109
+ * the caller is responsible for distinguishing these cases (typically via
110
+ * a follow-up `findByIdOrSlugIncludingTrashed` to surface NOT_FOUND vs
111
+ * NOT_TRASHED). The `AND deleted_at IS NOT NULL` clause is the safety net
112
+ * that prevents permanent delete from bypassing the trash workflow.
113
+ */
104
114
  permanentDelete(type: string, id: string): Promise<boolean>;
105
115
  /**
106
116
  * Find trashed content items
@@ -158,8 +168,14 @@ declare class ContentRepository {
158
168
  * Syncs the draft revision's data into the content table columns so the
159
169
  * content table always reflects the published version.
160
170
  * If no draft revision exists, creates one from current data and publishes it.
171
+ *
172
+ * `publishedAt` (optional) overrides the publication timestamp. If omitted,
173
+ * the existing `published_at` is preserved (idempotent re-publish keeps the
174
+ * original date) and falls back to the current time on first publish. Pass
175
+ * an explicit value to backdate a publish (e.g. when migrating content from
176
+ * another CMS).
161
177
  */
162
- publish(type: string, id: string): Promise<ContentItem>;
178
+ publish(type: string, id: string, publishedAt?: string): Promise<ContentItem>;
163
179
  /**
164
180
  * Unpublish content
165
181
  *
@@ -484,10 +500,14 @@ interface FieldDescriptor {
484
500
  kind: string;
485
501
  label?: string;
486
502
  required?: boolean;
503
+ /**
504
+ * For `select` / `multiSelect`: the list of enum choices.
505
+ * For `json` fields driven by a plugin `widget`: arbitrary widget config.
506
+ */
487
507
  options?: Array<{
488
508
  value: string;
489
509
  label: string;
490
- }>;
510
+ }> | Record<string, unknown>;
491
511
  }
492
512
  /**
493
513
  * Discriminated union for handler results.
@@ -651,7 +671,9 @@ declare function handleContentUnschedule(db: Kysely<Database>, collection: strin
651
671
  * (syncDataColumns, slug sync, status/revision update) that must
652
672
  * be atomic to prevent FTS shadow table corruption on crash.
653
673
  */
654
- declare function handleContentPublish(db: Kysely<Database>, collection: string, id: string): Promise<ApiResult<ContentResponse>>;
674
+ declare function handleContentPublish(db: Kysely<Database>, collection: string, id: string, options?: {
675
+ publishedAt?: string;
676
+ }): Promise<ApiResult<ContentResponse>>;
655
677
  /**
656
678
  * Unpublish content (revert to draft).
657
679
  *
@@ -823,6 +845,23 @@ declare class SchemaRegistry {
823
845
  * Get a collection with all its fields
824
846
  */
825
847
  getCollectionWithFields(slug: string): Promise<CollectionWithFields | null>;
848
+ /**
849
+ * List every collection together with its fields in O(1) query shapes
850
+ * — one for collections, then one batched query for the fields of every
851
+ * returned collection — instead of the N+1 pattern of `listCollections`
852
+ * + per-collection `listFields`. The fields query is chunked at
853
+ * `SQL_BATCH_SIZE` to stay under D1's bound-parameter limit, so on
854
+ * sites with more than `SQL_BATCH_SIZE` collections the field fetch
855
+ * becomes `ceil(collectionCount / SQL_BATCH_SIZE)` queries — still
856
+ * a constant factor, not N+1. Typical sites have well under
857
+ * `SQL_BATCH_SIZE` collections, so this is two queries in practice.
858
+ *
859
+ * Used by the manifest build, which previously paid N+1 round-trips on
860
+ * every admin request. Each round-trip costs ~80–150ms against the D1
861
+ * primary on a busy link, so a 10-collection site spent ~1 s rebuilding
862
+ * a manifest that is now built fresh per admin request (no cache).
863
+ */
864
+ listCollectionsWithFields(): Promise<CollectionWithFields[]>;
826
865
  /**
827
866
  * Create a new collection
828
867
  */
@@ -2018,7 +2057,7 @@ declare function parseWxrString(xml: string): Promise<WxrData>;
2018
2057
  * export default definePlugin({
2019
2058
  * id: "my-plugin",
2020
2059
  * version: "1.0.0",
2021
- * capabilities: ["read:content"],
2060
+ * capabilities: ["content:read"],
2022
2061
  * hooks: {
2023
2062
  * "content:beforeSave": async (event, ctx) => {
2024
2063
  * ctx.log.info("Saving content", { collection: event.collection });
@@ -2549,29 +2588,35 @@ interface EmDashConfig {
2549
2588
  */
2550
2589
  siteUrl?: string;
2551
2590
  /**
2552
- * Headers to trust for client IP resolution when running behind a reverse
2553
- * proxy. The first header in this list that is present on the request
2554
- * wins. Applies to rate limiting for auth endpoints and comment
2555
- * submission.
2591
+ * Additional origins accepted by passkey verification.
2556
2592
  *
2557
- * Common values:
2558
- * - `x-real-ip` nginx, Caddy, Traefik
2559
- * - `fly-client-ip` Fly.io
2560
- * - `x-forwarded-for` generic (first entry is used)
2593
+ * When the same EmDash deployment is reachable under several hostnames sharing
2594
+ * a registrable parent (e.g. `https://example.com` plus
2595
+ * `https://preview.example.com`), the canonical `siteUrl` defines the `rpId`
2596
+ * and the entries here are the *additional* origins from which assertions
2597
+ * are accepted. Each entry must be the same hostname as `siteUrl` or a
2598
+ * subdomain of it — WebAuthn requires `rpId` to be a registrable suffix of
2599
+ * every origin.
2561
2600
  *
2562
- * Only set this when you **control the reverse proxy**. Untrusted
2563
- * clients can set any header they like; trusting headers from an open
2564
- * network is an IP-spoofing vulnerability that defeats rate limiting.
2601
+ * Merged at runtime with the `EMDASH_ALLOWED_ORIGINS` env var (comma-separated).
2602
+ * Validation:
2603
+ * - Config-declared entries are shape-checked at Astro startup.
2604
+ * - Subdomain relationship to `siteUrl` is checked at startup when
2605
+ * `siteUrl` is also config-declared, otherwise at first passkey
2606
+ * verification (since `siteUrl` may come from `EMDASH_SITE_URL`).
2565
2607
  *
2566
- * On Cloudflare the `cf` object on the request is used automatically —
2567
- * you normally don't need to set this. Leave unset (or empty) to
2568
- * preserve the default: IP is resolved only when the request came
2569
- * through Cloudflare's edge.
2608
+ * Mismatches throw with a source-attributed message naming
2609
+ * `config.allowedOrigins` or `EMDASH_ALLOWED_ORIGINS`.
2570
2610
  *
2571
- * Falls back to `EMDASH_TRUSTED_PROXY_HEADERS` env var (comma-separated)
2572
- * when this option is not set, so operators can configure at deploy
2573
- * time without touching the Astro config.
2611
+ * @example
2612
+ * ```ts
2613
+ * emdash({
2614
+ * siteUrl: "https://example.com",
2615
+ * allowedOrigins: ["https://preview.example.com"],
2616
+ * })
2617
+ * ```
2574
2618
  */
2619
+ allowedOrigins?: string[];
2575
2620
  trustedProxyHeaders?: string[];
2576
2621
  /**
2577
2622
  * Enable playground mode for ephemeral "try EmDash" sites.
@@ -2720,10 +2765,20 @@ declare const pluginManifestSchema: z$1.ZodObject<{
2720
2765
  "read:media": "read:media";
2721
2766
  "write:media": "write:media";
2722
2767
  "read:users": "read:users";
2723
- "email:send": "email:send";
2724
2768
  "email:provide": "email:provide";
2725
2769
  "email:intercept": "email:intercept";
2726
2770
  "page:inject": "page:inject";
2771
+ "network:request": "network:request";
2772
+ "network:request:unrestricted": "network:request:unrestricted";
2773
+ "content:read": "content:read";
2774
+ "content:write": "content:write";
2775
+ "media:read": "media:read";
2776
+ "media:write": "media:write";
2777
+ "users:read": "users:read";
2778
+ "email:send": "email:send";
2779
+ "hooks.email-transport:register": "hooks.email-transport:register";
2780
+ "hooks.email-events:register": "hooks.email-events:register";
2781
+ "hooks.page-fragments:register": "hooks.page-fragments:register";
2727
2782
  }>>;
2728
2783
  allowedHosts: z$1.ZodArray<z$1.ZodString>;
2729
2784
  storage: z$1.ZodRecord<z$1.ZodString, z$1.ZodObject<{
@@ -4109,8 +4164,18 @@ interface GetPreviewUrlOptions {
4109
4164
  expiresIn?: string | number;
4110
4165
  /** Base URL of the site. If not provided, returns a relative URL. */
4111
4166
  baseUrl?: string;
4112
- /** Custom path pattern. Use {collection} and {id} as placeholders. Default: "/{collection}/{id}" */
4167
+ /**
4168
+ * Custom path pattern. Supports `{collection}`, `{id}` and `{locale}`
4169
+ * placeholders. Default: `"/{collection}/{id}"`.
4170
+ */
4113
4171
  pathPattern?: string;
4172
+ /**
4173
+ * Locale segment substituted for the `{locale}` placeholder in `pathPattern`.
4174
+ * Pass an empty string to omit the locale prefix (e.g. for the default locale
4175
+ * when `prefixDefaultLocale` is `false`); adjacent slashes left by an empty
4176
+ * value are collapsed and any trailing slash is trimmed.
4177
+ */
4178
+ locale?: string;
4114
4179
  }
4115
4180
  /**
4116
4181
  * Generate a preview URL for content
@@ -4322,6 +4387,8 @@ interface Menu {
4322
4387
  name: string;
4323
4388
  label: string;
4324
4389
  items: MenuItem[];
4390
+ locale: string;
4391
+ translationGroup: string | null;
4325
4392
  }
4326
4393
  /**
4327
4394
  * Input for creating a menu item
@@ -4329,6 +4396,11 @@ interface Menu {
4329
4396
  interface CreateMenuItemInput {
4330
4397
  type: MenuItemType;
4331
4398
  label: string;
4399
+ /**
4400
+ * Identifier of the referenced entity. For `reference_collection` items it is
4401
+ * the content's translation_group (locale-agnostic); for `taxonomy` items it
4402
+ * is the term's translation_group.
4403
+ */
4332
4404
  referenceCollection?: string;
4333
4405
  referenceId?: string;
4334
4406
  customUrl?: string;
@@ -4356,6 +4428,9 @@ interface UpdateMenuItemInput {
4356
4428
  interface CreateMenuInput {
4357
4429
  name: string;
4358
4430
  label: string;
4431
+ locale?: string;
4432
+ /** When set, links the new menu into an existing translation_group. */
4433
+ translationOf?: string;
4359
4434
  }
4360
4435
  /**
4361
4436
  * Input for updating a menu
@@ -4375,35 +4450,30 @@ interface ReorderMenuItemsInput {
4375
4450
  }
4376
4451
  //#endregion
4377
4452
  //#region src/menus/index.d.ts
4453
+ interface MenuQueryOptions {
4454
+ /** Override the locale used for the lookup. When omitted, the locale comes
4455
+ * from the request context or the configured defaultLocale. */
4456
+ locale?: string;
4457
+ }
4378
4458
  /**
4379
- * Get menu by name with resolved URLs
4459
+ * Get a menu by name with resolved URLs.
4380
4460
  *
4381
4461
  * @example
4382
4462
  * ```ts
4383
- * import { getMenu } from "emdash";
4384
- *
4385
4463
  * const menu = await getMenu("primary");
4386
- * if (menu) {
4387
- * console.log(menu.items); // Array of MenuItem with resolved URLs
4388
- * }
4464
+ * const menuEs = await getMenu("primary", { locale: "es" });
4389
4465
  * ```
4390
4466
  */
4391
- declare function getMenu(name: string): Promise<Menu | null>;
4467
+ declare function getMenu(name: string, options?: MenuQueryOptions): Promise<Menu | null>;
4392
4468
  /**
4393
- * Get all menus (without items - for admin list)
4394
- *
4395
- * @example
4396
- * ```ts
4397
- * import { getMenus } from "emdash";
4398
- *
4399
- * const menus = await getMenus();
4400
- * console.log(menus); // [{ id, name, label }]
4401
- * ```
4469
+ * Get all menus (without items, locale-filtered for admin list / site nav
4470
+ * summaries). When no locale is configured, returns menus across all locales.
4402
4471
  */
4403
- declare function getMenus(): Promise<Array<{
4472
+ declare function getMenus(options?: MenuQueryOptions): Promise<Array<{
4404
4473
  id: string;
4405
4474
  name: string;
4406
4475
  label: string;
4476
+ locale: string;
4407
4477
  }>>;
4408
4478
  //#endregion
4409
4479
  //#region src/bylines/index.d.ts
@@ -4450,6 +4520,8 @@ interface TaxonomyDef {
4450
4520
  labelSingular?: string;
4451
4521
  hierarchical: boolean;
4452
4522
  collections: string[];
4523
+ locale: string;
4524
+ translationGroup: string | null;
4453
4525
  }
4454
4526
  /**
4455
4527
  * Taxonomy term - a specific term within a taxonomy (e.g., "News" in "category")
@@ -4463,6 +4535,8 @@ interface TaxonomyTerm {
4463
4535
  description?: string;
4464
4536
  children: TaxonomyTerm[];
4465
4537
  count?: number;
4538
+ locale: string;
4539
+ translationGroup: string | null;
4466
4540
  }
4467
4541
  /**
4468
4542
  * Flat version for DB row
@@ -4474,6 +4548,8 @@ interface TaxonomyTermRow {
4474
4548
  label: string;
4475
4549
  parent_id: string | null;
4476
4550
  data: string | null;
4551
+ locale: string;
4552
+ translation_group: string | null;
4477
4553
  }
4478
4554
  /**
4479
4555
  * Input for creating a term
@@ -4483,6 +4559,10 @@ interface CreateTermInput {
4483
4559
  label: string;
4484
4560
  parentId?: string;
4485
4561
  description?: string;
4562
+ locale?: string;
4563
+ /** When set, links the new term into an existing translation_group (sourced
4564
+ * from the term being translated). */
4565
+ translationOf?: string;
4486
4566
  }
4487
4567
  /**
4488
4568
  * Input for updating a term
@@ -4495,64 +4575,59 @@ interface UpdateTermInput {
4495
4575
  }
4496
4576
  //#endregion
4497
4577
  //#region src/taxonomies/index.d.ts
4578
+ interface TaxonomyQueryOptions {
4579
+ locale?: string;
4580
+ }
4498
4581
  /**
4499
4582
  * No-op — kept for API compatibility.
4500
- *
4501
- * Used to invalidate a worker-lifetime "has any term assignments?" probe.
4502
- * That probe added a query on every cold isolate to save one query on
4503
- * sites with zero term assignments (i.e. the wrong tradeoff), so we
4504
- * dropped it. The batch term join below returns an empty map for empty
4505
- * sites at the same cost as the probe, without the pre-check.
4506
4583
  */
4507
4584
  declare function invalidateTermCache(): void;
4508
4585
  /**
4509
- * Get all taxonomy definitions
4586
+ * Get every taxonomy definition. Definitions are per-locale (one row per
4587
+ * locale inside the same translation_group) — by default we resolve to the
4588
+ * active locale.
4510
4589
  */
4511
- declare function getTaxonomyDefs(): Promise<TaxonomyDef[]>;
4590
+ declare function getTaxonomyDefs(options?: TaxonomyQueryOptions): Promise<TaxonomyDef[]>;
4512
4591
  /**
4513
- * Get a single taxonomy definition by name
4592
+ * Get a single taxonomy definition by name. Uses the fallback chain so even
4593
+ * if there is no translation for the active locale we still return something.
4594
+ *
4595
+ * If `getTaxonomyDefs()` has already loaded the full list in this request
4596
+ * (which happens during entry-term hydration on every page that renders a
4597
+ * collection), search the matching def in memory rather than running a
4598
+ * second query against `_emdash_taxonomy_defs`.
4514
4599
  */
4515
- declare function getTaxonomyDef(name: string): Promise<TaxonomyDef | null>;
4600
+ declare function getTaxonomyDef(name: string, options?: TaxonomyQueryOptions): Promise<TaxonomyDef | null>;
4516
4601
  /**
4517
- * Get all terms for a taxonomy (as tree for hierarchical, flat for tags)
4602
+ * All terms of a taxonomy in a specific locale (flat for non-hierarchical,
4603
+ * tree for hierarchical).
4518
4604
  */
4519
- declare function getTaxonomyTerms(taxonomyName: string): Promise<TaxonomyTerm[]>;
4605
+ declare function getTaxonomyTerms(taxonomyName: string, options?: TaxonomyQueryOptions): Promise<TaxonomyTerm[]>;
4520
4606
  /**
4521
- * Get a single term by taxonomy and slug
4607
+ * Get a single term by (taxonomy, slug). Honours the fallback chain — if the
4608
+ * slug exists in a fallback locale, we return that row (useful for deep-linking
4609
+ * to a term page when the translation is missing).
4522
4610
  */
4523
- declare function getTerm(taxonomyName: string, slug: string): Promise<TaxonomyTerm | null>;
4611
+ declare function getTerm(taxonomyName: string, slug: string, options?: TaxonomyQueryOptions): Promise<TaxonomyTerm | null>;
4524
4612
  /**
4525
- * Get terms assigned to an entry
4613
+ * Terms assigned to a content entry, resolved into the active locale. Terms
4614
+ * whose translation_group lacks a row in the requested locale are omitted.
4526
4615
  */
4527
- declare function getEntryTerms(collection: string, entryId: string, taxonomyName?: string): Promise<TaxonomyTerm[]>;
4616
+ declare function getEntryTerms(collection: string, entryId: string, taxonomyName?: string, options?: TaxonomyQueryOptions): Promise<TaxonomyTerm[]>;
4528
4617
  /**
4529
- * Get terms for multiple entries in a single query (batched API)
4530
- *
4531
- * This is more efficient than calling getEntryTerms for each entry
4532
- * when you need terms for a list of entries.
4533
- *
4534
- * @param collection - The collection type (e.g., "posts")
4535
- * @param entryIds - Array of entry IDs
4536
- * @param taxonomyName - The taxonomy name (e.g., "categories")
4537
- * @returns Map from entry ID to array of terms
4618
+ * Terms for multiple entries of one taxonomy, single query.
4538
4619
  */
4539
- declare function getTermsForEntries(collection: string, entryIds: string[], taxonomyName: string): Promise<Map<string, TaxonomyTerm[]>>;
4620
+ declare function getTermsForEntries(collection: string, entryIds: string[], taxonomyName: string, options?: TaxonomyQueryOptions): Promise<Map<string, TaxonomyTerm[]>>;
4540
4621
  /**
4541
- * Batch-fetch terms for multiple entries across ALL taxonomies in a single query.
4542
- *
4543
- * Returns a Map keyed by entry ID, where each value is a Record keyed by
4544
- * taxonomy name with the matching terms as an array. Used by
4545
- * getEmDashCollection to eagerly hydrate `entry.data.terms` and avoid
4546
- * the N+1 pattern that callers hit when they loop and call getEntryTerms.
4547
- *
4548
- * Pre-migration databases (content_taxonomies missing) return an empty
4549
- * Map — the join falls through to the `isMissingTableError` branch.
4622
+ * Batch-fetch terms for multiple entries across ALL taxonomies in one query.
4623
+ * Primes the request-cache for subsequent per-entry calls to `getEntryTerms`.
4550
4624
  */
4551
- declare function getAllTermsForEntries(collection: string, entryIds: string[]): Promise<Map<string, Record<string, TaxonomyTerm[]>>>;
4625
+ declare function getAllTermsForEntries(collection: string, entryIds: string[], options?: TaxonomyQueryOptions): Promise<Map<string, Record<string, TaxonomyTerm[]>>>;
4552
4626
  /**
4553
- * Get entries by term (wraps getEmDashCollection)
4627
+ * Get entries by term. Both the lookup (term slug in the active locale) and
4628
+ * the content query respect the active locale.
4554
4629
  */
4555
- declare function getEntriesByTerm(collection: string, taxonomyName: string, termSlug: string): Promise<Array<{
4630
+ declare function getEntriesByTerm(collection: string, taxonomyName: string, termSlug: string, options?: TaxonomyQueryOptions): Promise<Array<{
4556
4631
  id: string;
4557
4632
  data: Record<string, unknown>;
4558
4633
  }>>;
@@ -4952,4 +5027,4 @@ declare function extractPlainText(blocks: PortableTextBlock$1[] | string | null
4952
5027
  declare function extractSearchableFields(entry: Record<string, unknown>, fields: string[]): Record<string, string>;
4953
5028
  //#endregion
4954
5029
  export { UpdateMenuInput as $, EntryResult as $n, handleMediaList as $r, SuggestedAction as $t, getEntriesByTerm as A, ListResponse as Ai, WxrData as An, ProseMirrorNode as Ar, probeUrl as At, TaxonomyTerm as B, CreateMediaInput as Bi, getDb as Bn, CreateSectionInput as Br, ImportContext as Bt, ReorderWidgetsInput as C, handleContentUnpublish as Ci, AuthResult as Cn, PortableTextLinkMark as Cr, parseWxrDate as Ct, WidgetComponentDef as D, ContentListResponse as Di, WxrAttachment as Dn, PortableTextUnknownBlock as Dr, getFileSources as Dt, WidgetArea as E, ApiContext as Ei, definePlugin as En, PortableTextTextBlock as Er, getAllSources as Et, getTerm as F, FieldDefinition as Fi, parseWxrString as Fn, SandboxRunnerFactory as Fr, FetchOptions as Ft, getMenu as G, EmDashDatabaseError as Gi, WaitUntilFn as Gn, getCollectionInfo as Gr, OAuthInput as Gt, UpdateTermInput as H, MediaRepository as Hi, getFallbackChain as Hn, Section as Hr, ImportResult as Ht, getTermsForEntries as I, FieldUIHints as Ii, CollectionFilter as In, SandboxedPlugin as Ir, FieldCompatibility as It, CreateMenuItemInput as J, CollectionFilter$1 as Jn, MediaListResponse as Jr, ProbeResult as Jt, getMenus as K, after as Kn, SchemaError as Kr, PostTypeAnalysis as Kt, invalidateTermCache as L, FileValue as Li, EntryData as Ln, SerializedRequest as Lr, FileInput as Lt, getTaxonomyDef as M, portableText as Mi, WxrSite as Mn, SandboxEmailSendCallback as Mr, importReusableBlocksAsSections as Mt, getTaxonomyDefs as N, reference as Ni, WxrTag as Nn, SandboxOptions as Nr, AttachmentInfo as Nt, WidgetType as O, ContentResponse as Oi, WxrAuthor as On, ProseMirrorDocument as Or, getSource as Ot, getTaxonomyTerms as P, image as Pi, parseWxr as Pn, SandboxRunner as Pr, CollectionSchemaStatus as Pt, ReorderMenuItemsInput as Q, EmDashCollections as Qn, handleMediaGet as Qr, SourceProbeResult as Qt, CreateTermInput as R, ImageValue as Ri, EntryFilter as Rn, getSection as Rr, ImportAnalysis as Rt, PropDef as S, handleContentTranslations as Si, AuthProviderModule as Sn, PortableTextImageBlock as Sr, wordpressRestSource as St, Widget as T, handleContentUpdate as Ti, ExternalAuthConfig as Tn, PortableTextSpan as Tr, clearSources as Tt, getByline as U, ContentRepository as Ui, getI18nConfig as Un, SectionSource as Ur, ImportSource as Ut, TaxonomyTermRow as V, MediaItem as Vi, I18nConfig as Vn, GetSectionsOptions as Vr, ImportFieldDef as Vt, getBylineBySlug as W, DatabaseConfig as Wi, isI18nEnabled as Wn, UpdateSectionInput as Wr, NormalizedItem as Wt, MenuItem as X, ContentEntry as Xn, handleMediaCreate as Xr, SourceCapabilities as Xt, Menu as Y, CollectionResult as Yn, MediaResponse as Yr, SourceAuth as Yt, MenuItemType as Z, EditFieldMeta as Zn, handleMediaDelete as Zr, SourceInput as Zt, getWidgetArea as _, handleContentListTrashed as _i, S3StorageConfig as _n, computeContentHash as _r, VerifyPreviewTokenOptions as _t, search as a, handleRevisionRestore as ai, createPluginManager as an, getEmDashCollection as ar, getPluginSetting as at, CreateWidgetAreaInput as b, handleContentRestore as bi, AuthProviderAdminExports as bn, prosemirrorToPortableText as br, parseContentId as bt, FTSManager as c, handleContentCountScheduled as ci, HookPipeline as cn, resolveEmDashPath as cr, getSiteSettings as ct, SearchOptions as d, handleContentDelete as di, ValidatedPluginManifest as dn, FieldAnnotation as dr, isPreviewRequest as dt, handleMediaUpdate as ei, UrlInput as en, InferCollectionData as er, UpdateMenuItemInput as et, SearchResponse as f, handleContentDiscardDraft as fi, pluginManifestSchema as fn, createEditable as fr, GetPreviewUrlOptions as ft, Suggestion as g, handleContentList as gi, LocalStorageConfig as gn, sanitizeHref as gr, PreviewTokenPayload as gt, SuggestOptions as h, handleContentGetIncludingTrashed as hi, getStoredConfig as hn, isSafeHref as hr, GeneratePreviewTokenOptions as ht, getSuggestions as i, handleRevisionList as ii, PluginManager as in, getEditMeta as ir, getComments as it, getEntryTerms as j, ManifestResponse as ji, WxrPost as jn, SandboxEmailMessage as jr, registerSource as jt, getAllTermsForEntries as k, FieldDescriptor as ki, WxrCategory as kn, ProseMirrorMark as kr, getUrlSources as kt, CollectionSearchOptions as l, handleContentCountTrashed as li, HookResult as ln, CMSAnnotation as lr, setSiteSettings as lt, SearchStats as m, handleContentGet as mi, PluginDescriptor as mn, decodeSlug as mr, getPreviewUrl as mt, extractSearchableFields as n, RevisionResponse as ni, SandboxNotAvailableError as nn, TranslationSummary as nr, GetCommentsResult as nt, searchCollection as o, generateManifest as oi, PluginRouteError as on, getEmDashEntry as or, getPluginSettings as ot, SearchResult as p, handleContentDuplicate as pi, EmDashConfig as pn, createNoop as pr, buildPreviewUrl as pt, CreateMenuInput as q, CacheHint as qn, SchemaRegistry as qr, PostTypeMapping as qt, getSearchStats as r, handleRevisionGet as ri, createNoopSandboxRunner as rn, TranslationsResult as rr, getCommentCount as rt, searchWithDb as s, handleContentCompare as si, EmailPipeline as sn, getTranslations as sr, getSiteSetting as st, extractPlainText as t, RevisionListResponse as ti, NoopSandboxRunner as tn, ResolvePathResult as tr, GetCommentsOptions as tt, SearchConfig as u, handleContentCreate as ui, createHookPipeline as un, EditProxy as ur, getPreviewToken as ut, getWidgetAreas as v, handleContentPermanentDelete as vi, StorageDescriptor as vn, hashString as vr, VerifyPreviewTokenResult as vt, UpdateWidgetInput as w, handleContentUnschedule as wi, AuthRouteDescriptor as wn, PortableTextMarkDef as wr, wxrSource as wt, CreateWidgetInput as x, handleContentSchedule as xi, AuthProviderDescriptor as xn, PortableTextCodeBlock as xr, verifyPreviewToken as xt, getWidgetComponents as y, handleContentPublish as yi, AuthDescriptor as yn, portableTextToProsemirror as yr, generatePreviewToken as yt, TaxonomyDef as z, PortableTextBlock$2 as zi, emdashLoader as zn, getSections as zr, ImportConfig as zt };
4955
- //# sourceMappingURL=index-DIb-CzNx.d.mts.map
5030
+ //# sourceMappingURL=index-DjPMOfO0.d.mts.map