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,292 +1,59 @@
1
- import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
2
- import { r as RevisionRepository, t as ContentRepository } from "./content-BcQPYxdV.mjs";
1
+ import { i as __exportAll } from "./runner-DMnlIkh4.mjs";
2
+ import { n as getI18nConfig } from "./config-CVssduLe.mjs";
3
+ import { r as RevisionRepository, t as ContentRepository } from "./content-C7G4QXkK.mjs";
3
4
  import { t as MediaRepository } from "./media-D8FbNsl0.mjs";
4
- import { t as withTransaction } from "./transaction-Cn2rjY78.mjs";
5
- import { t as RedirectRepository } from "./redirect-D_pshWdf.mjs";
6
- import { t as BylineRepository } from "./byline-Chbr2GoP.mjs";
7
- import { i as FTSManager, n as SchemaRegistry } from "./registry-C3Mr0ODu.mjs";
8
- import { n as getDb } from "./loader-CndGj8kM.mjs";
9
- import { n as requestCached, t as peekRequestCache } from "./request-cache-Ci7f5pBb.mjs";
10
- import { t as validateSeed } from "./validate-CxVsLehf.mjs";
11
- import { sql } from "kysely";
5
+ import { t as TaxonomyRepository } from "./taxonomy-DSxx2K2L.mjs";
6
+ import { t as OptionsRepository } from "./options-nPxWnrya.mjs";
7
+ import { t as withTransaction } from "./transaction-D44LBXvU.mjs";
8
+ import { t as RedirectRepository } from "./redirect-C5H7VGIX.mjs";
9
+ import { t as BylineRepository } from "./byline-C3vnhIpU.mjs";
10
+ import { i as FTSManager, n as SchemaRegistry } from "./registry-Beb7wxFc.mjs";
11
+ import { r as getDb } from "./loader-Bx2_9-5e.mjs";
12
+ import { n as requestCached, t as peekRequestCache } from "./request-cache-C-tIpYIw.mjs";
13
+ import { t as validateSeed } from "./validate-CBIbxM3L.mjs";
12
14
  import { ulid } from "ulidx";
13
15
  import { imageSize } from "image-size";
14
16
  import mime from "mime/lite";
15
17
 
16
- //#region src/database/repositories/taxonomy.ts
18
+ //#region src/settings/index.ts
19
+ /** Prefix for site settings in the options table */
20
+ const SETTINGS_PREFIX = "site:";
21
+ const SITE_SETTINGS_CACHE_KEY = Symbol.for("emdash:site-settings");
22
+ const g = globalThis;
23
+ const holder = g[SITE_SETTINGS_CACHE_KEY] ?? (() => {
24
+ const h = {
25
+ version: 0,
26
+ cached: null,
27
+ cachedVersion: -1
28
+ };
29
+ g[SITE_SETTINGS_CACHE_KEY] = h;
30
+ return h;
31
+ })();
17
32
  /**
18
- * Taxonomy repository for categories, tags, and other classification
33
+ * Bump the isolate-wide site-settings cache version, forcing the next
34
+ * `getSiteSettings()` to re-query the database.
19
35
  *
20
- * Taxonomies are hierarchical (via parentId) and can be attached to content entries.
36
+ * Called from every `site:*` write path. Other isolates still serve their
37
+ * own cached copy until they expire — staleness bounded by isolate lifetime.
21
38
  */
22
- var TaxonomyRepository = class {
23
- constructor(db) {
24
- this.db = db;
25
- }
26
- /**
27
- * Create a new taxonomy term
28
- */
29
- async create(input) {
30
- const id = ulid();
31
- const parentId = input.parentId === void 0 || input.parentId === "" ? null : input.parentId;
32
- const row = {
33
- id,
34
- name: input.name,
35
- slug: input.slug,
36
- label: input.label,
37
- parent_id: parentId,
38
- data: input.data ? JSON.stringify(input.data) : null
39
- };
40
- await this.db.insertInto("taxonomies").values(row).execute();
41
- const taxonomy = await this.findById(id);
42
- if (!taxonomy) throw new Error("Failed to create taxonomy");
43
- return taxonomy;
44
- }
45
- /**
46
- * Find taxonomy by ID
47
- */
48
- async findById(id) {
49
- const row = await this.db.selectFrom("taxonomies").selectAll().where("id", "=", id).executeTakeFirst();
50
- return row ? this.rowToTaxonomy(row) : null;
51
- }
52
- /**
53
- * Find taxonomy by name and slug (unique constraint)
54
- */
55
- async findBySlug(name, slug) {
56
- const row = await this.db.selectFrom("taxonomies").selectAll().where("name", "=", name).where("slug", "=", slug).executeTakeFirst();
57
- return row ? this.rowToTaxonomy(row) : null;
58
- }
59
- /**
60
- * Get all terms for a taxonomy (e.g., all categories)
61
- */
62
- async findByName(name, options = {}) {
63
- let query = this.db.selectFrom("taxonomies").selectAll().where("name", "=", name).orderBy("label", "asc").orderBy("id", "asc");
64
- if (options.parentId !== void 0) if (options.parentId === null) query = query.where("parent_id", "is", null);
65
- else query = query.where("parent_id", "=", options.parentId);
66
- return (await query.execute()).map((row) => this.rowToTaxonomy(row));
67
- }
68
- /**
69
- * Get children of a taxonomy term
70
- */
71
- async findChildren(parentId) {
72
- return (await this.db.selectFrom("taxonomies").selectAll().where("parent_id", "=", parentId).orderBy("label", "asc").orderBy("id", "asc").execute()).map((row) => this.rowToTaxonomy(row));
73
- }
74
- /**
75
- * Update a taxonomy term
76
- */
77
- async update(id, input) {
78
- if (!await this.findById(id)) return null;
79
- const updates = {};
80
- if (input.slug !== void 0) updates.slug = input.slug;
81
- if (input.label !== void 0) updates.label = input.label;
82
- if (input.parentId !== void 0) updates.parent_id = input.parentId === "" ? null : input.parentId;
83
- if (input.data !== void 0) updates.data = JSON.stringify(input.data);
84
- if (Object.keys(updates).length > 0) await this.db.updateTable("taxonomies").set(updates).where("id", "=", id).execute();
85
- return this.findById(id);
86
- }
87
- /**
88
- * Delete a taxonomy term
89
- */
90
- async delete(id) {
91
- await this.db.deleteFrom("content_taxonomies").where("taxonomy_id", "=", id).execute();
92
- return ((await this.db.deleteFrom("taxonomies").where("id", "=", id).executeTakeFirst()).numDeletedRows ?? 0) > 0;
93
- }
94
- /**
95
- * Attach a taxonomy term to a content entry
96
- */
97
- async attachToEntry(collection, entryId, taxonomyId) {
98
- const row = {
99
- collection,
100
- entry_id: entryId,
101
- taxonomy_id: taxonomyId
102
- };
103
- await this.db.insertInto("content_taxonomies").values(row).onConflict((oc) => oc.doNothing()).execute();
104
- }
105
- /**
106
- * Detach a taxonomy term from a content entry
107
- */
108
- async detachFromEntry(collection, entryId, taxonomyId) {
109
- await this.db.deleteFrom("content_taxonomies").where("collection", "=", collection).where("entry_id", "=", entryId).where("taxonomy_id", "=", taxonomyId).execute();
110
- }
111
- /**
112
- * Get all taxonomy terms for a content entry
113
- */
114
- async getTermsForEntry(collection, entryId, taxonomyName) {
115
- let query = this.db.selectFrom("content_taxonomies").innerJoin("taxonomies", "taxonomies.id", "content_taxonomies.taxonomy_id").selectAll("taxonomies").where("content_taxonomies.collection", "=", collection).where("content_taxonomies.entry_id", "=", entryId);
116
- if (taxonomyName) query = query.where("taxonomies.name", "=", taxonomyName);
117
- return (await query.execute()).map((row) => this.rowToTaxonomy(row));
118
- }
119
- /**
120
- * Set all taxonomy terms for a content entry (replaces existing)
121
- * Uses batch operations to avoid N+1 queries.
122
- */
123
- async setTermsForEntry(collection, entryId, taxonomyName, taxonomyIds) {
124
- const current = await this.getTermsForEntry(collection, entryId, taxonomyName);
125
- const currentIds = new Set(current.map((t) => t.id));
126
- const newIds = new Set(taxonomyIds);
127
- const toRemove = current.filter((t) => !newIds.has(t.id)).map((t) => t.id);
128
- if (toRemove.length > 0) await this.db.deleteFrom("content_taxonomies").where("collection", "=", collection).where("entry_id", "=", entryId).where("taxonomy_id", "in", toRemove).execute();
129
- const toAdd = taxonomyIds.filter((id) => !currentIds.has(id));
130
- if (toAdd.length > 0) await this.db.insertInto("content_taxonomies").values(toAdd.map((taxonomy_id) => ({
131
- collection,
132
- entry_id: entryId,
133
- taxonomy_id
134
- }))).onConflict((oc) => oc.doNothing()).execute();
135
- }
136
- /**
137
- * Remove all taxonomy associations for an entry (use when entry is deleted)
138
- */
139
- async clearEntryTerms(collection, entryId) {
140
- const result = await this.db.deleteFrom("content_taxonomies").where("collection", "=", collection).where("entry_id", "=", entryId).executeTakeFirst();
141
- return Number(result.numDeletedRows ?? 0);
142
- }
143
- /**
144
- * Count entries that have a specific taxonomy term
145
- */
146
- async countEntriesWithTerm(taxonomyId) {
147
- const result = await this.db.selectFrom("content_taxonomies").select((eb) => eb.fn.count("entry_id").as("count")).where("taxonomy_id", "=", taxonomyId).executeTakeFirst();
148
- return Number(result?.count || 0);
149
- }
150
- /**
151
- * Convert database row to Taxonomy object
152
- */
153
- rowToTaxonomy(row) {
154
- return {
155
- id: row.id,
156
- name: row.name,
157
- slug: row.slug,
158
- label: row.label,
159
- parentId: row.parent_id,
160
- data: row.data ? JSON.parse(row.data) : null
161
- };
162
- }
163
- };
164
-
165
- //#endregion
166
- //#region src/database/repositories/options.ts
167
- function escapeLike(value) {
168
- return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
39
+ function invalidateSiteSettingsCache() {
40
+ holder.version++;
41
+ holder.cached = null;
42
+ holder.cachedVersion = -1;
169
43
  }
170
44
  /**
171
- * Options repository for key-value settings storage
172
- *
173
- * Used for site settings, plugin configuration, and other arbitrary key-value data.
174
- * Values are stored as JSON for flexibility.
175
- */
176
- var OptionsRepository = class {
177
- constructor(db) {
178
- this.db = db;
179
- }
180
- /**
181
- * Get an option value
182
- */
183
- async get(name) {
184
- const row = await this.db.selectFrom("options").select("value").where("name", "=", name).executeTakeFirst();
185
- if (!row) return null;
186
- return JSON.parse(row.value);
187
- }
188
- /**
189
- * Get an option value with a default
190
- */
191
- async getOrDefault(name, defaultValue) {
192
- return await this.get(name) ?? defaultValue;
193
- }
194
- /**
195
- * Set an option value (creates or updates)
196
- */
197
- async set(name, value) {
198
- const row = {
199
- name,
200
- value: JSON.stringify(value)
201
- };
202
- await this.db.insertInto("options").values(row).onConflict((oc) => oc.column("name").doUpdateSet({ value: row.value })).execute();
203
- }
204
- /**
205
- * Set an option value only if no row with that name exists. Atomic at the
206
- * database level via INSERT ... ON CONFLICT DO NOTHING, so concurrent
207
- * callers can't race past the check.
208
- *
209
- * Returns true when the row was inserted, false when a row already
210
- * existed (regardless of its value — even an empty string or null).
211
- */
212
- async setIfAbsent(name, value) {
213
- const row = {
214
- name,
215
- value: JSON.stringify(value)
216
- };
217
- return ((await this.db.insertInto("options").values(row).onConflict((oc) => oc.column("name").doNothing()).executeTakeFirst()).numInsertedOrUpdatedRows ?? 0n) > 0n;
218
- }
219
- /**
220
- * Delete an option
221
- */
222
- async delete(name) {
223
- return ((await this.db.deleteFrom("options").where("name", "=", name).executeTakeFirst()).numDeletedRows ?? 0) > 0;
224
- }
225
- /**
226
- * Check if an option exists
227
- */
228
- async exists(name) {
229
- return !!await this.db.selectFrom("options").select("name").where("name", "=", name).executeTakeFirst();
230
- }
231
- /**
232
- * Get multiple options at once
233
- */
234
- async getMany(names) {
235
- if (names.length === 0) return /* @__PURE__ */ new Map();
236
- const rows = await this.db.selectFrom("options").select(["name", "value"]).where("name", "in", names).execute();
237
- const result = /* @__PURE__ */ new Map();
238
- for (const row of rows) result.set(row.name, JSON.parse(row.value));
239
- return result;
240
- }
241
- /**
242
- * Set multiple options at once
243
- */
244
- async setMany(options) {
245
- const entries = Object.entries(options);
246
- if (entries.length === 0) return;
247
- for (const [name, value] of entries) await this.set(name, value);
248
- }
249
- /**
250
- * Get all options (use sparingly)
251
- */
252
- async getAll() {
253
- const rows = await this.db.selectFrom("options").select(["name", "value"]).execute();
254
- const result = /* @__PURE__ */ new Map();
255
- for (const row of rows) result.set(row.name, JSON.parse(row.value));
256
- return result;
257
- }
258
- /**
259
- * Get all options matching a prefix
260
- */
261
- async getByPrefix(prefix) {
262
- const pattern = `${escapeLike(prefix)}%`;
263
- const rows = await this.db.selectFrom("options").select(["name", "value"]).where(sql`name LIKE ${pattern} ESCAPE '\\'`).execute();
264
- const result = /* @__PURE__ */ new Map();
265
- for (const row of rows) result.set(row.name, JSON.parse(row.value));
266
- return result;
267
- }
268
- /**
269
- * Delete all options matching a prefix
270
- */
271
- async deleteByPrefix(prefix) {
272
- const pattern = `${escapeLike(prefix)}%`;
273
- const result = await this.db.deleteFrom("options").where(sql`name LIKE ${pattern} ESCAPE '\\'`).executeTakeFirst();
274
- return Number(result.numDeletedRows ?? 0);
275
- }
276
- };
277
-
278
- //#endregion
279
- //#region src/settings/index.ts
280
- /** Prefix for site settings in the options table */
281
- const SETTINGS_PREFIX = "site:";
282
- /**
283
45
  * Type guard for MediaReference values
284
46
  */
285
47
  function isMediaReference(value) {
286
48
  return typeof value === "object" && value !== null && "mediaId" in value;
287
49
  }
288
50
  /**
289
- * Resolve a media reference to include the full URL
51
+ * Resolve a media reference to include the full URL plus content metadata.
52
+ *
53
+ * Pulls `mimeType` and intrinsic dimensions from the media row so callers
54
+ * can emit correct head tags (e.g. `<link rel="icon" type="image/svg+xml">`,
55
+ * which Chromium requires when the URL has no `.svg` extension) without
56
+ * a second round-trip to the media table.
290
57
  */
291
58
  async function resolveMediaReference(mediaRef, db, _storage) {
292
59
  if (!mediaRef?.mediaId) return mediaRef;
@@ -294,7 +61,10 @@ async function resolveMediaReference(mediaRef, db, _storage) {
294
61
  const media = await new MediaRepository(db).findById(mediaRef.mediaId);
295
62
  if (media) return {
296
63
  ...mediaRef,
297
- url: `/_emdash/api/media/file/${media.storageKey}`
64
+ url: `/_emdash/api/media/file/${media.storageKey}`,
65
+ contentType: media.mimeType,
66
+ ...media.width !== null ? { width: media.width } : {},
67
+ ...media.height !== null ? { height: media.height } : {}
298
68
  };
299
69
  } catch {}
300
70
  return mediaRef;
@@ -352,8 +122,21 @@ async function getSiteSettingWithDb(key, db, storage = null) {
352
122
  * ```
353
123
  */
354
124
  function getSiteSettings() {
355
- return requestCached("siteSettings", async () => {
356
- return getSiteSettingsWithDb(await getDb());
125
+ return requestCached("siteSettings", () => {
126
+ const versionAtCall = holder.version;
127
+ if (holder.cached && holder.cachedVersion === versionAtCall) return holder.cached;
128
+ const fetchPromise = (async () => {
129
+ return getSiteSettingsWithDb(await getDb());
130
+ })().catch((error) => {
131
+ if (holder.cached === fetchPromise) {
132
+ holder.cached = null;
133
+ holder.cachedVersion = -1;
134
+ }
135
+ throw error;
136
+ });
137
+ holder.cached = fetchPromise;
138
+ holder.cachedVersion = versionAtCall;
139
+ return fetchPromise;
357
140
  });
358
141
  }
359
142
  /**
@@ -400,7 +183,11 @@ async function setSiteSettings(settings, db) {
400
183
  const options = new OptionsRepository(db);
401
184
  const updates = {};
402
185
  for (const [key, value] of Object.entries(settings)) if (value !== void 0) updates[`${SETTINGS_PREFIX}${key}`] = value;
403
- await options.setMany(updates);
186
+ try {
187
+ await options.setMany(updates);
188
+ } finally {
189
+ invalidateSiteSettingsCache();
190
+ }
404
191
  }
405
192
  /**
406
193
  * Get a single plugin setting by key.
@@ -945,49 +732,78 @@ async function applySeed(db, seed, options = {}) {
945
732
  }
946
733
  }
947
734
  }
948
- if (seed.taxonomies) for (const taxonomy of seed.taxonomies) {
949
- const existingDef = await db.selectFrom("_emdash_taxonomy_defs").selectAll().where("name", "=", taxonomy.name).executeTakeFirst();
950
- if (existingDef) {
951
- if (onConflict === "error") throw new Error(`Conflict: taxonomy "${taxonomy.name}" already exists`);
952
- if (onConflict === "update") await db.updateTable("_emdash_taxonomy_defs").set({
953
- label: taxonomy.label,
954
- label_singular: taxonomy.labelSingular ?? null,
955
- hierarchical: taxonomy.hierarchical ? 1 : 0,
956
- collections: JSON.stringify(taxonomy.collections)
957
- }).where("id", "=", existingDef.id).execute();
958
- } else {
959
- await db.insertInto("_emdash_taxonomy_defs").values({
960
- id: ulid(),
961
- name: taxonomy.name,
962
- label: taxonomy.label,
963
- label_singular: taxonomy.labelSingular ?? null,
964
- hierarchical: taxonomy.hierarchical ? 1 : 0,
965
- collections: JSON.stringify(taxonomy.collections)
966
- }).execute();
967
- result.taxonomies.created++;
968
- }
969
- if (taxonomy.terms && taxonomy.terms.length > 0) {
970
- const termRepo = new TaxonomyRepository(db);
971
- if (taxonomy.hierarchical) await applyHierarchicalTerms(termRepo, taxonomy.name, taxonomy.terms, result, onConflict);
972
- else for (const term of taxonomy.terms) {
973
- const existing = await termRepo.findBySlug(taxonomy.name, term.slug);
974
- if (existing) {
975
- if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomy.name}" already exists`);
976
- if (onConflict === "update") {
977
- await termRepo.update(existing.id, {
735
+ if (seed.taxonomies) {
736
+ const defSeedIdMap = /* @__PURE__ */ new Map();
737
+ const termSeedIdMap = /* @__PURE__ */ new Map();
738
+ const fallbackLocale = getI18nConfig()?.defaultLocale ?? "en";
739
+ for (const taxonomy of seed.taxonomies) {
740
+ const defLocale = taxonomy.locale ?? fallbackLocale;
741
+ const existingDef = await db.selectFrom("_emdash_taxonomy_defs").selectAll().where("name", "=", taxonomy.name).where("locale", "=", defLocale).executeTakeFirst();
742
+ let defId;
743
+ let defTranslationGroup;
744
+ if (existingDef) {
745
+ defId = existingDef.id;
746
+ defTranslationGroup = existingDef.translation_group ?? existingDef.id;
747
+ if (onConflict === "error") throw new Error(`Conflict: taxonomy "${taxonomy.name}" (${defLocale}) already exists`);
748
+ if (onConflict === "update") await db.updateTable("_emdash_taxonomy_defs").set({
749
+ label: taxonomy.label,
750
+ label_singular: taxonomy.labelSingular ?? null,
751
+ hierarchical: taxonomy.hierarchical ? 1 : 0,
752
+ collections: JSON.stringify(taxonomy.collections)
753
+ }).where("id", "=", existingDef.id).execute();
754
+ } else {
755
+ defId = ulid();
756
+ defTranslationGroup = defId;
757
+ if (taxonomy.translationOf) {
758
+ const source = defSeedIdMap.get(taxonomy.translationOf);
759
+ if (source) defTranslationGroup = source.translationGroup;
760
+ else console.warn(`taxonomy "${taxonomy.name}" (${defLocale}): translationOf "${taxonomy.translationOf}" not found yet; minting a fresh group.`);
761
+ }
762
+ await db.insertInto("_emdash_taxonomy_defs").values({
763
+ id: defId,
764
+ name: taxonomy.name,
765
+ label: taxonomy.label,
766
+ label_singular: taxonomy.labelSingular ?? null,
767
+ hierarchical: taxonomy.hierarchical ? 1 : 0,
768
+ collections: JSON.stringify(taxonomy.collections),
769
+ locale: defLocale,
770
+ translation_group: defTranslationGroup
771
+ }).execute();
772
+ result.taxonomies.created++;
773
+ }
774
+ if (taxonomy.id) defSeedIdMap.set(taxonomy.id, {
775
+ id: defId,
776
+ translationGroup: defTranslationGroup
777
+ });
778
+ if (taxonomy.terms && taxonomy.terms.length > 0) {
779
+ const termRepo = new TaxonomyRepository(db);
780
+ if (taxonomy.hierarchical) await applyHierarchicalTerms(termRepo, taxonomy.name, defLocale, taxonomy.terms, termSeedIdMap, result, onConflict);
781
+ else for (const term of taxonomy.terms) {
782
+ const termLocale = term.locale ?? defLocale;
783
+ const existing = await termRepo.findBySlug(taxonomy.name, term.slug, termLocale);
784
+ if (existing) {
785
+ if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomy.name}" (${termLocale}) already exists`);
786
+ if (onConflict === "update") {
787
+ await termRepo.update(existing.id, {
788
+ label: term.label,
789
+ data: term.description ? { description: term.description } : {}
790
+ });
791
+ result.taxonomies.terms++;
792
+ }
793
+ if (term.id) termSeedIdMap.set(term.id, existing.id);
794
+ } else {
795
+ const translationOf = term.translationOf ? termSeedIdMap.get(term.translationOf) : void 0;
796
+ const created = await termRepo.create({
797
+ name: taxonomy.name,
798
+ slug: term.slug,
978
799
  label: term.label,
979
- data: term.description ? { description: term.description } : {}
800
+ data: term.description ? { description: term.description } : void 0,
801
+ locale: termLocale,
802
+ translationOf
980
803
  });
804
+ if (term.id) termSeedIdMap.set(term.id, created.id);
981
805
  result.taxonomies.terms++;
982
806
  }
983
- } else {
984
- await termRepo.create({
985
- name: taxonomy.name,
986
- slug: term.slug,
987
- label: term.label,
988
- data: term.description ? { description: term.description } : void 0
989
- });
990
- result.taxonomies.terms++;
991
807
  }
992
808
  }
993
809
  }
@@ -1090,25 +906,44 @@ async function applySeed(db, seed, options = {}) {
1090
906
  result.content.created++;
1091
907
  }
1092
908
  }
1093
- if (seed.menus) for (const menu of seed.menus) {
1094
- const existingMenu = await db.selectFrom("_emdash_menus").selectAll().where("name", "=", menu.name).executeTakeFirst();
1095
- let menuId;
1096
- if (existingMenu) {
1097
- menuId = existingMenu.id;
1098
- await db.deleteFrom("_emdash_menu_items").where("menu_id", "=", menuId).execute();
1099
- } else {
1100
- menuId = ulid();
1101
- await db.insertInto("_emdash_menus").values({
909
+ if (seed.menus) {
910
+ const menuSeedIdMap = /* @__PURE__ */ new Map();
911
+ const fallbackLocale = getI18nConfig()?.defaultLocale ?? "en";
912
+ for (const menu of seed.menus) {
913
+ const locale = menu.locale ?? fallbackLocale;
914
+ const existingMenu = await db.selectFrom("_emdash_menus").selectAll().where("name", "=", menu.name).where("locale", "=", locale).executeTakeFirst();
915
+ let menuId;
916
+ let translationGroup;
917
+ if (existingMenu) {
918
+ menuId = existingMenu.id;
919
+ translationGroup = existingMenu.translation_group ?? existingMenu.id;
920
+ await db.deleteFrom("_emdash_menu_items").where("menu_id", "=", menuId).execute();
921
+ } else {
922
+ menuId = ulid();
923
+ translationGroup = menuId;
924
+ if (menu.translationOf) {
925
+ const source = menuSeedIdMap.get(menu.translationOf);
926
+ if (source) translationGroup = source.translationGroup;
927
+ else console.warn(`menu "${menu.name}" (${locale}): translationOf "${menu.translationOf}" not found yet; minting a fresh group.`);
928
+ }
929
+ await db.insertInto("_emdash_menus").values({
930
+ id: menuId,
931
+ name: menu.name,
932
+ label: menu.label,
933
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
934
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
935
+ locale,
936
+ translation_group: translationGroup
937
+ }).execute();
938
+ result.menus.created++;
939
+ }
940
+ if (menu.id) menuSeedIdMap.set(menu.id, {
1102
941
  id: menuId,
1103
- name: menu.name,
1104
- label: menu.label,
1105
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
1106
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
1107
- }).execute();
1108
- result.menus.created++;
942
+ translationGroup
943
+ });
944
+ const itemCount = await applyMenuItems(db, menuId, locale, menu.items, null, 0, seedIdMap);
945
+ result.menus.items += itemCount;
1109
946
  }
1110
- const itemCount = await applyMenuItems(db, menuId, menu.items, null, 0, seedIdMap);
1111
- result.menus.items += itemCount;
1112
947
  }
1113
948
  if (seed.redirects) {
1114
949
  const redirectRepo = new RedirectRepository(db);
@@ -1207,9 +1042,9 @@ async function applySeed(db, seed, options = {}) {
1207
1042
  }
1208
1043
  }
1209
1044
  }
1210
- const { invalidateBylineCache } = await import("./bylines-CRNsVG88.mjs").then((n) => n.t);
1211
- const { invalidateRedirectCache } = await import("./cache-BkKBuIvS.mjs").then((n) => n.t);
1212
- const { invalidateUrlPatternCache } = await import("./query-fqEdLFms.mjs").then((n) => n.o);
1045
+ const { invalidateBylineCache } = await import("./bylines-esI7ioa9.mjs").then((n) => n.t);
1046
+ const { invalidateRedirectCache } = await import("./cache-fTzxgMFJ.mjs").then((n) => n.t);
1047
+ const { invalidateUrlPatternCache } = await import("./query-Bo-msrmu.mjs").then((n) => n.o);
1213
1048
  invalidateBylineCache();
1214
1049
  invalidateRedirectCache();
1215
1050
  invalidateUrlPatternCache();
@@ -1218,17 +1053,22 @@ async function applySeed(db, seed, options = {}) {
1218
1053
  /**
1219
1054
  * Apply hierarchical taxonomy terms (parents before children)
1220
1055
  */
1221
- async function applyHierarchicalTerms(termRepo, taxonomyName, terms, result, onConflict = "skip") {
1056
+ async function applyHierarchicalTerms(termRepo, taxonomyName, defLocale, terms, termSeedIdMap, result, onConflict = "skip") {
1222
1057
  const slugToId = /* @__PURE__ */ new Map();
1223
1058
  let remaining = [...terms];
1224
1059
  let maxPasses = 10;
1225
1060
  while (remaining.length > 0 && maxPasses > 0) {
1226
1061
  const processedThisPass = [];
1227
- for (const term of remaining) if (!term.parent || slugToId.has(term.parent)) {
1228
- const parentId = term.parent ? slugToId.get(term.parent) : void 0;
1229
- const existing = await termRepo.findBySlug(taxonomyName, term.slug);
1062
+ for (const term of remaining) {
1063
+ const termLocale = term.locale ?? defLocale;
1064
+ const parentReady = !term.parent || slugToId.has(`${termLocale}::${term.parent}`);
1065
+ const translationReady = !term.translationOf || termSeedIdMap.has(term.translationOf);
1066
+ if (!parentReady || !translationReady) continue;
1067
+ const parentId = term.parent ? slugToId.get(`${termLocale}::${term.parent}`) : void 0;
1068
+ const translationOf = term.translationOf ? termSeedIdMap.get(term.translationOf) : void 0;
1069
+ const existing = await termRepo.findBySlug(taxonomyName, term.slug, termLocale);
1230
1070
  if (existing) {
1231
- if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomyName}" already exists`);
1071
+ if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomyName}" (${termLocale}) already exists`);
1232
1072
  if (onConflict === "update") {
1233
1073
  await termRepo.update(existing.id, {
1234
1074
  label: term.label,
@@ -1237,24 +1077,28 @@ async function applyHierarchicalTerms(termRepo, taxonomyName, terms, result, onC
1237
1077
  });
1238
1078
  result.taxonomies.terms++;
1239
1079
  }
1240
- slugToId.set(term.slug, existing.id);
1080
+ slugToId.set(`${termLocale}::${term.slug}`, existing.id);
1081
+ if (term.id) termSeedIdMap.set(term.id, existing.id);
1241
1082
  } else {
1242
1083
  const created = await termRepo.create({
1243
1084
  name: taxonomyName,
1244
1085
  slug: term.slug,
1245
1086
  label: term.label,
1246
1087
  parentId,
1247
- data: term.description ? { description: term.description } : void 0
1088
+ data: term.description ? { description: term.description } : void 0,
1089
+ locale: termLocale,
1090
+ translationOf
1248
1091
  });
1249
- slugToId.set(term.slug, created.id);
1092
+ slugToId.set(`${termLocale}::${term.slug}`, created.id);
1093
+ if (term.id) termSeedIdMap.set(term.id, created.id);
1250
1094
  result.taxonomies.terms++;
1251
1095
  }
1252
- processedThisPass.push(term.slug);
1096
+ processedThisPass.push(term.slug + "::" + termLocale);
1253
1097
  }
1254
- remaining = remaining.filter((t) => !processedThisPass.includes(t.slug));
1098
+ remaining = remaining.filter((t) => !processedThisPass.includes(t.slug + "::" + (t.locale ?? defLocale)));
1255
1099
  maxPasses--;
1256
1100
  }
1257
- if (remaining.length > 0) console.warn(`Could not process ${remaining.length} terms due to missing parents`);
1101
+ if (remaining.length > 0) console.warn(`Could not process ${remaining.length} terms due to missing parents/translations`);
1258
1102
  }
1259
1103
  /**
1260
1104
  * Apply byline credits to a content entry.
@@ -1284,7 +1128,7 @@ async function applyContentTaxonomies(db, collectionSlug, contentId, entry, isUp
1284
1128
  if (isUpdate) await db.deleteFrom("content_taxonomies").where("collection", "=", collectionSlug).where("entry_id", "=", contentId).execute();
1285
1129
  if (!entry.taxonomies) {
1286
1130
  if (isUpdate) {
1287
- const { invalidateTermCache } = await import("./taxonomies-B4IAshV8.mjs").then((n) => n.u);
1131
+ const { invalidateTermCache } = await import("./taxonomies-CTtewrSQ.mjs").then((n) => n.u);
1288
1132
  invalidateTermCache();
1289
1133
  }
1290
1134
  return;
@@ -1296,13 +1140,19 @@ async function applyContentTaxonomies(db, collectionSlug, contentId, entry, isUp
1296
1140
  if (term) await termRepo.attachToEntry(collectionSlug, contentId, term.id);
1297
1141
  }
1298
1142
  }
1299
- const { invalidateTermCache } = await import("./taxonomies-B4IAshV8.mjs").then((n) => n.u);
1143
+ const { invalidateTermCache } = await import("./taxonomies-CTtewrSQ.mjs").then((n) => n.u);
1300
1144
  invalidateTermCache();
1301
1145
  }
1302
1146
  /**
1303
- * Apply menu items recursively
1147
+ * Apply menu items recursively.
1148
+ *
1149
+ * Each item gets a fresh `translation_group` (= its own id). The seed format's
1150
+ * `SeedMenuItem` has no `id`/`translationOf` fields, so we can't express the
1151
+ * cross-locale "same nav entry" link here — items diverge across locales on
1152
+ * re-apply. Runtime navigation still resolves correctly because `reference_id`
1153
+ * already holds the content's translation_group.
1304
1154
  */
1305
- async function applyMenuItems(db, menuId, items, parentId, startOrder, seedIdMap) {
1155
+ async function applyMenuItems(db, menuId, locale, items, parentId, startOrder, seedIdMap) {
1306
1156
  let count = 0;
1307
1157
  let order = startOrder;
1308
1158
  for (const item of items) {
@@ -1328,12 +1178,14 @@ async function applyMenuItems(db, menuId, items, parentId, startOrder, seedIdMap
1328
1178
  title_attr: item.titleAttr ?? null,
1329
1179
  target: item.target ?? null,
1330
1180
  css_classes: item.cssClasses ?? null,
1331
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1181
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1182
+ locale,
1183
+ translation_group: itemId
1332
1184
  }).execute();
1333
1185
  count++;
1334
1186
  order++;
1335
1187
  if (item.children && item.children.length > 0) {
1336
- const childCount = await applyMenuItems(db, menuId, item.children, itemId, 0, seedIdMap);
1188
+ const childCount = await applyMenuItems(db, menuId, locale, item.children, itemId, 0, seedIdMap);
1337
1189
  count += childCount;
1338
1190
  }
1339
1191
  }
@@ -1524,5 +1376,5 @@ function getImageDimensions(buffer) {
1524
1376
  }
1525
1377
 
1526
1378
  //#endregion
1527
- export { ssrfSafeFetch as a, getPluginSetting as c, getSiteSettings as d, setSiteSettings as f, resolveAndValidateExternalUrl as i, getPluginSettings as l, TaxonomyRepository as m, apply_exports as n, stripCredentialHeaders as o, OptionsRepository as p, SsrfError as r, validateExternalUrl as s, applySeed as t, getSiteSetting as u };
1528
- //# sourceMappingURL=apply-x0eMK1lX.mjs.map
1379
+ export { ssrfSafeFetch as a, getPluginSetting as c, getSiteSettings as d, setSiteSettings as f, resolveAndValidateExternalUrl as i, getPluginSettings as l, apply_exports as n, stripCredentialHeaders as o, SsrfError as r, validateExternalUrl as s, applySeed as t, getSiteSetting as u };
1380
+ //# sourceMappingURL=apply-UsrFuO7l.mjs.map