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,4 +1,4 @@
1
- import { t as Interceptor } from "../transport-CUnEL3Vs.mjs";
1
+ import { t as Interceptor } from "../transport-DX_5rpsq.mjs";
2
2
 
3
3
  //#region src/client/cf-access.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as tokenInterceptor, i as devBypassInterceptor, n as createTransport, r as csrfInterceptor, t as Interceptor } from "../transport-CUnEL3Vs.mjs";
1
+ import { a as tokenInterceptor, i as devBypassInterceptor, n as createTransport, r as csrfInterceptor, t as Interceptor } from "../transport-DX_5rpsq.mjs";
2
2
 
3
3
  //#region src/client/portable-text.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as tokenInterceptor, c as markdownToPortableText, i as refreshInterceptor, l as portableTextToMarkdown, n as csrfInterceptor, o as convertDataForRead, r as devBypassInterceptor, s as convertDataForWrite, t as createTransport } from "../transport-C9ugt2Nr.mjs";
1
+ import { a as tokenInterceptor, c as markdownToPortableText, i as refreshInterceptor, l as portableTextToMarkdown, n as csrfInterceptor, o as convertDataForRead, r as devBypassInterceptor, s as convertDataForWrite, t as createTransport } from "../transport-xpzIjCIB.mjs";
2
2
  import mime from "mime/lite";
3
3
 
4
4
  //#region src/client/index.ts
@@ -262,7 +262,7 @@ var EmDashClient = class {
262
262
  }
263
263
  /** List taxonomies */
264
264
  async taxonomies() {
265
- return (await this.request("GET", "/taxonomies")).items;
265
+ return (await this.request("GET", "/taxonomies")).taxonomies;
266
266
  }
267
267
  /** List terms in a taxonomy */
268
268
  async terms(taxonomy, options) {
@@ -278,7 +278,7 @@ var EmDashClient = class {
278
278
  }
279
279
  /** List menus */
280
280
  async menus() {
281
- return (await this.request("GET", "/menus")).items;
281
+ return this.request("GET", "/menus");
282
282
  }
283
283
  /** Get a menu with its items */
284
284
  async menu(name) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * EmDashClient — typed HTTP client for the EmDash REST API.\n *\n * Handles auth, CSRF, PT ↔ Markdown conversion, and optional `_rev`\n * concurrency tokens. Shared foundation for the CLI and future MCP server.\n *\n * @example\n * ```ts\n * import { EmDashClient } from \"emdash/client\";\n *\n * const client = new EmDashClient({\n * baseUrl: \"http://localhost:4321\",\n * devBypass: true,\n * });\n *\n * const posts = await client.list(\"posts\", { status: \"published\" });\n * ```\n */\n\nimport mime from \"mime/lite\";\n\nimport type { PortableTextBlock, FieldSchema } from \"./portable-text.js\";\nimport { convertDataForRead, convertDataForWrite } from \"./portable-text.js\";\nimport type { Interceptor } from \"./transport.js\";\nimport {\n\tcreateTransport,\n\tcsrfInterceptor,\n\tdevBypassInterceptor,\n\trefreshInterceptor,\n\ttokenInterceptor,\n} from \"./transport.js\";\n\n// Regex patterns for client utilities\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction mimeFromFilename(filename: string): string {\n\treturn mime.getType(filename) ?? \"application/octet-stream\";\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface EmDashClientOptions {\n\t/** Base URL of the EmDash instance */\n\tbaseUrl: string;\n\t/** API token (ec_pat_...) or OAuth token (ec_oat_...) */\n\ttoken?: string;\n\t/** OAuth refresh token for auto-refresh on 401 */\n\trefreshToken?: string;\n\t/** Called when a token is refreshed (for persisting new access token) */\n\tonTokenRefresh?: (accessToken: string, expiresIn: number) => void;\n\t/** Use dev-bypass authentication (localhost only) */\n\tdevBypass?: boolean;\n\t/** Additional request interceptors */\n\tinterceptors?: Interceptor[];\n}\n\n/** Standard API error shape */\nexport interface ApiError {\n\tcode: string;\n\tmessage: string;\n\tdetails?: Record<string, unknown>;\n}\n\n/** Standard API response wrapper */\nexport interface ClientResponse<T> {\n\tsuccess: true;\n\tdata: T;\n}\n\n/** Paginated list response */\nexport interface ListResult<T> {\n\titems: T[];\n\tnextCursor?: string;\n}\n\n/** Content item as returned by the API */\nexport interface ContentItem {\n\tid: string;\n\ttype: string;\n\tslug: string | null;\n\tstatus: string;\n\tdata: Record<string, unknown>;\n\tauthorId: string | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n\tpublishedAt: string | null;\n\tscheduledAt: string | null;\n\tliveRevisionId: string | null;\n\tdraftRevisionId: string | null;\n\tlocale: string | null;\n\ttranslationGroup: string | null;\n\t_rev?: string;\n}\n\n/** Collection metadata */\nexport interface Collection {\n\tslug: string;\n\tlabel: string;\n\tlabelSingular: string;\n\tdescription?: string;\n\ticon?: string;\n\tsupports: string[];\n}\n\n/** Collection with fields */\nexport interface CollectionWithFields extends Collection {\n\tfields: Field[];\n}\n\n/** Field metadata */\nexport interface Field {\n\tslug: string;\n\tlabel: string;\n\ttype: string;\n\trequired: boolean;\n\tunique: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: unknown;\n\twidget?: string;\n\toptions?: unknown;\n\tsortOrder?: number;\n}\n\n/** Media item */\nexport interface MediaItem {\n\tid: string;\n\tfilename: string;\n\tkey: string;\n\tmimeType: string;\n\tsize: number;\n\twidth?: number;\n\theight?: number;\n\talt?: string;\n\tcaption?: string;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/** Search result */\nexport interface SearchResult {\n\tid: string;\n\tcollection: string;\n\ttitle: string;\n\texcerpt?: string;\n\tscore: number;\n}\n\n/** Taxonomy */\nexport interface Taxonomy {\n\tname: string;\n\tlabel: string;\n\thierarchical: boolean;\n}\n\n/** Taxonomy term */\nexport interface Term {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\tparentId?: string | null;\n\tdescription?: string;\n\tcount?: number;\n}\n\n/** Menu */\nexport interface Menu {\n\tname: string;\n\tlabel: string;\n}\n\n/** Menu with items */\nexport interface MenuWithItems extends Menu {\n\titems: MenuItem[];\n}\n\n/** Menu item */\nexport interface MenuItem {\n\tid: string;\n\ttype: string;\n\tlabel: string;\n\tcustomUrl?: string;\n\treferenceCollection?: string;\n\treferenceId?: string;\n\ttarget?: string;\n\tparentId?: string | null;\n\tsortOrder: number;\n}\n\n/** Full schema export (returned by /api/schema) */\nexport interface SchemaExport {\n\tcollections: Array<{\n\t\tslug: string;\n\t\tlabel: string;\n\t\tlabelSingular: string;\n\t\tdescription?: string;\n\t\ticon?: string;\n\t\tsupports: string[];\n\t\tfields: Array<{\n\t\t\tslug: string;\n\t\t\tlabel: string;\n\t\t\ttype: string;\n\t\t\trequired: boolean;\n\t\t\tunique: boolean;\n\t\t\tdefaultValue?: unknown;\n\t\t\tvalidation?: unknown;\n\t\t\twidget?: string;\n\t\t\toptions?: unknown;\n\t\t}>;\n\t}>;\n\tversion: string;\n}\n\n/** Manifest — full schema + field descriptors */\nexport interface Manifest {\n\tversion: string;\n\thash: string;\n\tcollections: Record<\n\t\tstring,\n\t\t{\n\t\t\tlabel: string;\n\t\t\tlabelSingular: string;\n\t\t\tsupports: string[];\n\t\t\tfields: Record<string, { kind: string; label?: string; required?: boolean }>;\n\t\t}\n\t>;\n}\n\n// ---------------------------------------------------------------------------\n// Client errors\n// ---------------------------------------------------------------------------\n\nexport class EmDashApiError extends Error {\n\tconstructor(\n\t\tpublic readonly status: number,\n\t\tpublic readonly code: string,\n\t\tmessage: string,\n\t\tpublic readonly details?: Record<string, unknown>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"EmDashApiError\";\n\t}\n}\n\nexport class EmDashClientError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"EmDashClientError\";\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Client\n// ---------------------------------------------------------------------------\n\nexport class EmDashClient {\n\tprivate readonly baseUrl: string;\n\tprivate readonly transport: { fetch: (request: Request) => Promise<Response> };\n\n\t/** Cached field schemas per collection for PT conversion */\n\tprivate fieldSchemaCache = new Map<string, FieldSchema[]>();\n\n\tconstructor(options: EmDashClientOptions) {\n\t\tthis.baseUrl = options.baseUrl.replace(TRAILING_SLASH_PATTERN, \"\");\n\n\t\t// Build interceptor chain\n\t\tconst interceptors: Interceptor[] = [csrfInterceptor()];\n\n\t\tif (options.token) {\n\t\t\tinterceptors.push(tokenInterceptor(options.token));\n\t\t} else if (options.devBypass) {\n\t\t\tinterceptors.push(devBypassInterceptor(this.baseUrl));\n\t\t}\n\n\t\t// Auto-refresh expired OAuth tokens\n\t\tif (options.refreshToken) {\n\t\t\tinterceptors.push(\n\t\t\t\trefreshInterceptor({\n\t\t\t\t\trefreshToken: options.refreshToken,\n\t\t\t\t\ttokenEndpoint: `${this.baseUrl}/_emdash/api/oauth/token/refresh`,\n\t\t\t\t\tonTokenRefreshed: options.onTokenRefresh\n\t\t\t\t\t\t? (accessToken, _refreshToken, expiresAt) => {\n\t\t\t\t\t\t\t\tconst expiresIn = Math.floor((new Date(expiresAt).getTime() - Date.now()) / 1000);\n\t\t\t\t\t\t\t\toptions.onTokenRefresh!(accessToken, expiresIn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tif (options.interceptors) {\n\t\t\tinterceptors.push(...options.interceptors);\n\t\t}\n\n\t\tthis.transport = createTransport({ interceptors });\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Schema\n\t// -----------------------------------------------------------------------\n\n\t/** List all collections */\n\tasync collections(): Promise<Collection[]> {\n\t\tconst data = await this.request<{ items: Collection[] }>(\"GET\", \"/schema/collections\");\n\t\treturn data.items;\n\t}\n\n\t/** Get a single collection with its fields */\n\tasync collection(slug: string): Promise<CollectionWithFields> {\n\t\tconst data = await this.request<{ item: CollectionWithFields }>(\n\t\t\t\"GET\",\n\t\t\t`/schema/collections/${encodeURIComponent(slug)}?includeFields=true`,\n\t\t);\n\t\tconst col = data.item;\n\t\t// Cache field schemas for PT conversion\n\t\tif (col.fields) {\n\t\t\tthis.fieldSchemaCache.set(\n\t\t\t\tslug,\n\t\t\t\tcol.fields.map((f) => ({ slug: f.slug, type: f.type })),\n\t\t\t);\n\t\t}\n\t\treturn col;\n\t}\n\n\t/** Create a collection */\n\tasync createCollection(input: {\n\t\tslug: string;\n\t\tlabel: string;\n\t\tlabelSingular?: string;\n\t\tdescription?: string;\n\t\ticon?: string;\n\t\tsupports?: string[];\n\t}): Promise<Collection> {\n\t\tconst data = await this.request<{ item: Collection }>(\"POST\", \"/schema/collections\", input);\n\t\treturn data.item;\n\t}\n\n\t/** Delete a collection */\n\tasync deleteCollection(slug: string): Promise<void> {\n\t\tawait this.request<unknown>(\"DELETE\", `/schema/collections/${encodeURIComponent(slug)}`);\n\t}\n\n\t/** Create a field on a collection */\n\tasync createField(\n\t\tcollection: string,\n\t\tinput: {\n\t\t\tslug: string;\n\t\t\ttype: string;\n\t\t\tlabel: string;\n\t\t\trequired?: boolean;\n\t\t\tunique?: boolean;\n\t\t\tdefaultValue?: unknown;\n\t\t\tvalidation?: unknown;\n\t\t\twidget?: string;\n\t\t\toptions?: unknown;\n\t\t\tsortOrder?: number;\n\t\t},\n\t): Promise<Field> {\n\t\tconst data = await this.request<{ item: Field }>(\n\t\t\t\"POST\",\n\t\t\t`/schema/collections/${encodeURIComponent(collection)}/fields`,\n\t\t\tinput,\n\t\t);\n\t\t// Invalidate field cache\n\t\tthis.fieldSchemaCache.delete(collection);\n\t\treturn data.item;\n\t}\n\n\t/** Delete a field from a collection */\n\tasync deleteField(collection: string, fieldSlug: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"DELETE\",\n\t\t\t`/schema/collections/${encodeURIComponent(collection)}/fields/${encodeURIComponent(fieldSlug)}`,\n\t\t);\n\t\tthis.fieldSchemaCache.delete(collection);\n\t}\n\n\t/** Get full manifest (schema + field descriptors + features) */\n\tasync manifest(): Promise<Manifest> {\n\t\treturn this.request<Manifest>(\"GET\", \"/manifest\");\n\t}\n\n\t/** Export full schema as JSON (used by `emdash types`) */\n\tasync schemaExport(): Promise<SchemaExport> {\n\t\treturn this.request<SchemaExport>(\"GET\", \"/schema\");\n\t}\n\n\t/** Export schema as TypeScript type definitions (used by `emdash types`) */\n\tasync schemaTypes(): Promise<string> {\n\t\tconst response = await this.requestRaw(\"GET\", \"/schema?format=typescript\");\n\t\tawait this.assertOk(response);\n\t\treturn response.text();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Content\n\t// -----------------------------------------------------------------------\n\n\t/** List content in a collection */\n\tasync list(\n\t\tcollection: string,\n\t\toptions?: {\n\t\t\tstatus?: string;\n\t\t\tlimit?: number;\n\t\t\tcursor?: string;\n\t\t\torderBy?: string;\n\t\t\torder?: \"asc\" | \"desc\";\n\t\t\tlocale?: string;\n\t\t},\n\t): Promise<ListResult<ContentItem>> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.status) params.set(\"status\", options.status);\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\t\tif (options?.cursor) params.set(\"cursor\", options.cursor);\n\t\tif (options?.orderBy) params.set(\"orderBy\", options.orderBy);\n\t\tif (options?.order) params.set(\"order\", options.order);\n\t\tif (options?.locale) params.set(\"locale\", options.locale);\n\n\t\tconst qs = params.toString();\n\t\tconst path = `/content/${encodeURIComponent(collection)}${qs ? `?${qs}` : \"\"}`;\n\t\treturn this.request<ListResult<ContentItem>>(\"GET\", path);\n\t}\n\n\t/** Async iterator that auto-follows cursors */\n\tasync *listAll(\n\t\tcollection: string,\n\t\toptions?: {\n\t\t\tstatus?: string;\n\t\t\tlimit?: number;\n\t\t\torderBy?: string;\n\t\t\torder?: \"asc\" | \"desc\";\n\t\t\tlocale?: string;\n\t\t},\n\t): AsyncGenerator<ContentItem> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.list(collection, { ...options, cursor });\n\t\t\tfor (const item of result.items) {\n\t\t\t\tyield item;\n\t\t\t}\n\t\t\tcursor = result.nextCursor;\n\t\t} while (cursor);\n\t}\n\n\t/**\n\t * Get a single content item. Returns the item with a `_rev` token\n\t * that can be passed to update() for optimistic concurrency.\n\t */\n\tasync get(\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions?: { raw?: boolean; locale?: string },\n\t): Promise<ContentItem> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.locale) params.set(\"locale\", options.locale);\n\t\tconst qs = params.size > 0 ? `?${params}` : \"\";\n\t\tconst result = await this.requestRaw(\n\t\t\t\"GET\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}${qs}`,\n\t\t);\n\t\tif (!result.ok) {\n\t\t\tawait this.assertOk(result);\n\t\t}\n\n\t\tconst raw = (await result.json()) as { data: { item: ContentItem; _rev?: string } };\n\t\tconst json = raw.data;\n\t\tconst item = json.item;\n\n\t\t// Attach _rev to the item so callers can pass it back on update\n\t\tif (json._rev) {\n\t\t\titem._rev = json._rev;\n\t\t}\n\n\t\t// Convert PT fields to markdown unless raw is requested\n\t\tif (!options?.raw && item.data) {\n\t\t\tconst fields = await this.getFieldSchemas(collection);\n\t\t\titem.data = convertDataForRead(item.data, fields, false);\n\t\t}\n\n\t\treturn item;\n\t}\n\n\t/** Create a new content item */\n\tasync create(\n\t\tcollection: string,\n\t\tinput: {\n\t\t\tdata: Record<string, unknown>;\n\t\t\tslug?: string;\n\t\t\tstatus?: string;\n\t\t\tlocale?: string;\n\t\t\ttranslationOf?: string;\n\t\t},\n\t): Promise<ContentItem> {\n\t\t// Convert markdown strings to PT for portableText fields\n\t\tconst fields = await this.getFieldSchemas(collection);\n\t\tconst data = convertDataForWrite(input.data, fields);\n\n\t\tconst result = await this.request<{ item: ContentItem }>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}`,\n\t\t\t{ ...input, data },\n\t\t);\n\t\treturn result.item;\n\t}\n\n\t/**\n\t * Update a content item. Pass `_rev` from a prior get() for optimistic\n\t * concurrency — the server returns 409 if the item has changed.\n\t * Omit `_rev` for a blind write (no conflict detection).\n\t */\n\tasync update(\n\t\tcollection: string,\n\t\tid: string,\n\t\tinput: {\n\t\t\tdata?: Record<string, unknown>;\n\t\t\tslug?: string;\n\t\t\tstatus?: string;\n\t\t\t_rev?: string;\n\t\t},\n\t): Promise<ContentItem> {\n\t\t// Convert markdown strings to PT\n\t\tlet data = input.data;\n\t\tif (data) {\n\t\t\tconst fields = await this.getFieldSchemas(collection);\n\t\t\tdata = convertDataForWrite(data, fields);\n\t\t}\n\n\t\tconst body = {\n\t\t\tdata,\n\t\t\tslug: input.slug,\n\t\t\tstatus: input.status,\n\t\t\t...(input._rev ? { _rev: input._rev } : {}),\n\t\t};\n\t\tconst result = await this.request<{ item: ContentItem; _rev?: string }>(\n\t\t\t\"PUT\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,\n\t\t\tbody,\n\t\t);\n\n\t\tconst item = result.item;\n\t\tif (result._rev) {\n\t\t\titem._rev = result._rev;\n\t\t}\n\t\treturn item;\n\t}\n\n\t/** Delete (soft) a content item */\n\tasync delete(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"DELETE\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\t/** Publish a content item */\n\tasync publish(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/publish`,\n\t\t);\n\t}\n\n\t/** Unpublish a content item */\n\tasync unpublish(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/unpublish`,\n\t\t);\n\t}\n\n\t/** Schedule publishing */\n\tasync schedule(collection: string, id: string, options: { at: string }): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/schedule`,\n\t\t\t{ scheduledAt: options.at },\n\t\t);\n\t}\n\n\t/** Restore a trashed content item */\n\tasync restore(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/restore`,\n\t\t);\n\t}\n\n\t/** Compare live and draft revisions */\n\tasync compare(\n\t\tcollection: string,\n\t\tid: string,\n\t): Promise<{\n\t\thasChanges: boolean;\n\t\tlive: Record<string, unknown> | null;\n\t\tdraft: Record<string, unknown> | null;\n\t}> {\n\t\treturn this.request<{\n\t\t\thasChanges: boolean;\n\t\t\tlive: Record<string, unknown> | null;\n\t\t\tdraft: Record<string, unknown> | null;\n\t\t}>(\"GET\", `/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/compare`);\n\t}\n\n\t/** Discard draft revision, reverting to the published version */\n\tasync discardDraft(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/discard-draft`,\n\t\t);\n\t}\n\n\t/**\n\t * Get all translations of a content item.\n\t * Returns the translation group ID and a summary of each locale version.\n\t */\n\tasync translations(\n\t\tcollection: string,\n\t\tid: string,\n\t): Promise<{\n\t\ttranslationGroup: string;\n\t\ttranslations: Array<{\n\t\t\tid: string;\n\t\t\tlocale: string | null;\n\t\t\tslug: string | null;\n\t\t\tstatus: string;\n\t\t\tupdatedAt: string;\n\t\t}>;\n\t}> {\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/translations`,\n\t\t);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Media\n\t// -----------------------------------------------------------------------\n\n\t/** List media items */\n\tasync mediaList(options?: {\n\t\tmimeType?: string;\n\t\tlimit?: number;\n\t\tcursor?: string;\n\t}): Promise<ListResult<MediaItem>> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.mimeType) params.set(\"mimeType\", options.mimeType);\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\t\tif (options?.cursor) params.set(\"cursor\", options.cursor);\n\n\t\tconst qs = params.toString();\n\t\treturn this.request<ListResult<MediaItem>>(\"GET\", `/media${qs ? `?${qs}` : \"\"}`);\n\t}\n\n\t/** Get a single media item */\n\tasync mediaGet(id: string): Promise<MediaItem> {\n\t\tconst data = await this.request<{ item: MediaItem }>(\"GET\", `/media/${encodeURIComponent(id)}`);\n\t\treturn data.item;\n\t}\n\n\t/** Upload a media file */\n\tasync mediaUpload(\n\t\tfile: Uint8Array | Blob,\n\t\tfilename: string,\n\t\toptions?: { alt?: string; caption?: string; contentType?: string },\n\t): Promise<MediaItem> {\n\t\tconst formData = new FormData();\n\n\t\t// Handle different file types\n\t\tif (file instanceof Blob) {\n\t\t\tformData.append(\"file\", file, filename);\n\t\t} else {\n\t\t\tconst mimeType = options?.contentType ?? mimeFromFilename(filename);\n\t\t\tformData.append(\"file\", new Blob([file as BlobPart], { type: mimeType }), filename);\n\t\t}\n\n\t\tif (options?.alt) formData.append(\"alt\", options.alt);\n\t\tif (options?.caption) formData.append(\"caption\", options.caption);\n\n\t\tconst url = `${this.baseUrl}/_emdash/api/media`;\n\t\tconst request = new Request(url, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: formData,\n\t\t});\n\n\t\tconst response = await this.transport.fetch(request);\n\t\tawait this.assertOk(response);\n\n\t\tconst raw = (await response.json()) as { data: { item: MediaItem } };\n\t\treturn raw.data.item;\n\t}\n\n\t/** Delete a media item */\n\tasync mediaDelete(id: string): Promise<void> {\n\t\tawait this.request<unknown>(\"DELETE\", `/media/${encodeURIComponent(id)}`);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Search\n\t// -----------------------------------------------------------------------\n\n\t/** Full-text search */\n\tasync search(\n\t\tquery: string,\n\t\toptions?: { collection?: string; locale?: string; limit?: number },\n\t): Promise<SearchResult[]> {\n\t\tconst params = new URLSearchParams({ q: query });\n\t\tif (options?.collection) params.set(\"collections\", options.collection);\n\t\tif (options?.locale) params.set(\"locale\", options.locale);\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\n\t\tconst data = await this.request<{ items: SearchResult[] }>(\"GET\", `/search?${params}`);\n\t\treturn data.items;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Taxonomies\n\t// -----------------------------------------------------------------------\n\n\t/** List taxonomies */\n\tasync taxonomies(): Promise<Taxonomy[]> {\n\t\tconst data = await this.request<{ items: Taxonomy[] }>(\"GET\", \"/taxonomies\");\n\t\treturn data.items;\n\t}\n\n\t/** List terms in a taxonomy */\n\tasync terms(\n\t\ttaxonomy: string,\n\t\toptions?: { limit?: number; cursor?: string },\n\t): Promise<ListResult<Term>> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\t\tif (options?.cursor) params.set(\"cursor\", options.cursor);\n\n\t\tconst qs = params.toString();\n\t\treturn this.request<ListResult<Term>>(\n\t\t\t\"GET\",\n\t\t\t`/taxonomies/${encodeURIComponent(taxonomy)}/terms${qs ? `?${qs}` : \"\"}`,\n\t\t);\n\t}\n\n\t/** Create a taxonomy term */\n\tasync createTerm(\n\t\ttaxonomy: string,\n\t\tinput: { slug: string; label: string; parentId?: string; description?: string },\n\t): Promise<Term> {\n\t\treturn this.request<Term>(\"POST\", `/taxonomies/${encodeURIComponent(taxonomy)}/terms`, input);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Menus\n\t// -----------------------------------------------------------------------\n\n\t/** List menus */\n\tasync menus(): Promise<Menu[]> {\n\t\tconst data = await this.request<{ items: Menu[] }>(\"GET\", \"/menus\");\n\t\treturn data.items;\n\t}\n\n\t/** Get a menu with its items */\n\tasync menu(name: string): Promise<MenuWithItems> {\n\t\treturn this.request<MenuWithItems>(\"GET\", `/menus/${encodeURIComponent(name)}`);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\t/** Make a typed JSON request to the API */\n\tprivate async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n\t\tconst response = await this.requestRaw(method, path, body);\n\t\tawait this.assertOk(response);\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\t/** Make a raw request — caller handles response */\n\tprivate async requestRaw(method: string, path: string, body?: unknown): Promise<Response> {\n\t\tconst url = `${this.baseUrl}/_emdash/api${path}`;\n\t\tconst headers: Record<string, string> = {\n\t\t\tAccept: \"application/json\",\n\t\t};\n\n\t\tlet requestBody: string | undefined;\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\trequestBody = JSON.stringify(body);\n\t\t}\n\n\t\tconst request = new Request(url, {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tbody: requestBody,\n\t\t});\n\n\t\treturn this.transport.fetch(request);\n\t}\n\n\t/** Assert a response is OK, throw typed error if not */\n\tprivate async assertOk(response: Response): Promise<void> {\n\t\tif (response.ok) return;\n\n\t\tlet code = \"UNKNOWN_ERROR\";\n\t\tlet message = `HTTP ${response.status}`;\n\t\tlet details: Record<string, unknown> | undefined;\n\n\t\ttry {\n\t\t\tconst json = (await response.json()) as {\n\t\t\t\terror?: { code?: string; message?: string; details?: Record<string, unknown> };\n\t\t\t};\n\t\t\tif (json.error) {\n\t\t\t\tcode = json.error.code ?? code;\n\t\t\t\tmessage = json.error.message ?? message;\n\t\t\t\tdetails = json.error.details;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Response body isn't JSON — use status text\n\t\t\tmessage = response.statusText || message;\n\t\t}\n\n\t\tthrow new EmDashApiError(response.status, code, message, details);\n\t}\n\n\t/** Get cached field schemas for a collection, fetching if needed */\n\tprivate async getFieldSchemas(collection: string): Promise<FieldSchema[]> {\n\t\tlet cached = this.fieldSchemaCache.get(collection);\n\t\tif (cached) return cached;\n\n\t\ttry {\n\t\t\tconst col = await this.collection(collection);\n\t\t\tcached = col.fields.map((f) => ({ slug: f.slug, type: f.type }));\n\t\t\tthis.fieldSchemaCache.set(collection, cached);\n\t\t\treturn cached;\n\t\t} catch {\n\t\t\t// If we can't fetch the schema, skip conversion\n\t\t\treturn [];\n\t\t}\n\t}\n}\n\n// Re-export transport types for interceptor authors\nexport type { Interceptor } from \"./transport.js\";\nexport {\n\tcreateTransport,\n\tcsrfInterceptor,\n\ttokenInterceptor,\n\tdevBypassInterceptor,\n} from \"./transport.js\";\nexport { portableTextToMarkdown, markdownToPortableText } from \"./portable-text.js\";\nexport type { PortableTextBlock } from \"./portable-text.js\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * EmDashClient — typed HTTP client for the EmDash REST API.\n *\n * Handles auth, CSRF, PT ↔ Markdown conversion, and optional `_rev`\n * concurrency tokens. Shared foundation for the CLI and future MCP server.\n *\n * @example\n * ```ts\n * import { EmDashClient } from \"emdash/client\";\n *\n * const client = new EmDashClient({\n * baseUrl: \"http://localhost:4321\",\n * devBypass: true,\n * });\n *\n * const posts = await client.list(\"posts\", { status: \"published\" });\n * ```\n */\n\nimport mime from \"mime/lite\";\n\nimport type { PortableTextBlock, FieldSchema } from \"./portable-text.js\";\nimport { convertDataForRead, convertDataForWrite } from \"./portable-text.js\";\nimport type { Interceptor } from \"./transport.js\";\nimport {\n\tcreateTransport,\n\tcsrfInterceptor,\n\tdevBypassInterceptor,\n\trefreshInterceptor,\n\ttokenInterceptor,\n} from \"./transport.js\";\n\n// Regex patterns for client utilities\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction mimeFromFilename(filename: string): string {\n\treturn mime.getType(filename) ?? \"application/octet-stream\";\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface EmDashClientOptions {\n\t/** Base URL of the EmDash instance */\n\tbaseUrl: string;\n\t/** API token (ec_pat_...) or OAuth token (ec_oat_...) */\n\ttoken?: string;\n\t/** OAuth refresh token for auto-refresh on 401 */\n\trefreshToken?: string;\n\t/** Called when a token is refreshed (for persisting new access token) */\n\tonTokenRefresh?: (accessToken: string, expiresIn: number) => void;\n\t/** Use dev-bypass authentication (localhost only) */\n\tdevBypass?: boolean;\n\t/** Additional request interceptors */\n\tinterceptors?: Interceptor[];\n}\n\n/** Standard API error shape */\nexport interface ApiError {\n\tcode: string;\n\tmessage: string;\n\tdetails?: Record<string, unknown>;\n}\n\n/** Standard API response wrapper */\nexport interface ClientResponse<T> {\n\tsuccess: true;\n\tdata: T;\n}\n\n/** Paginated list response */\nexport interface ListResult<T> {\n\titems: T[];\n\tnextCursor?: string;\n}\n\n/** Content item as returned by the API */\nexport interface ContentItem {\n\tid: string;\n\ttype: string;\n\tslug: string | null;\n\tstatus: string;\n\tdata: Record<string, unknown>;\n\tauthorId: string | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n\tpublishedAt: string | null;\n\tscheduledAt: string | null;\n\tliveRevisionId: string | null;\n\tdraftRevisionId: string | null;\n\tlocale: string | null;\n\ttranslationGroup: string | null;\n\t_rev?: string;\n}\n\n/** Collection metadata */\nexport interface Collection {\n\tslug: string;\n\tlabel: string;\n\tlabelSingular: string;\n\tdescription?: string;\n\ticon?: string;\n\tsupports: string[];\n}\n\n/** Collection with fields */\nexport interface CollectionWithFields extends Collection {\n\tfields: Field[];\n}\n\n/** Field metadata */\nexport interface Field {\n\tslug: string;\n\tlabel: string;\n\ttype: string;\n\trequired: boolean;\n\tunique: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: unknown;\n\twidget?: string;\n\toptions?: unknown;\n\tsortOrder?: number;\n}\n\n/** Media item */\nexport interface MediaItem {\n\tid: string;\n\tfilename: string;\n\tkey: string;\n\tmimeType: string;\n\tsize: number;\n\twidth?: number;\n\theight?: number;\n\talt?: string;\n\tcaption?: string;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/** Search result */\nexport interface SearchResult {\n\tid: string;\n\tcollection: string;\n\ttitle: string;\n\texcerpt?: string;\n\tscore: number;\n}\n\n/** Taxonomy */\nexport interface Taxonomy {\n\tname: string;\n\tlabel: string;\n\thierarchical: boolean;\n}\n\n/** Taxonomy term */\nexport interface Term {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\tparentId?: string | null;\n\tdescription?: string;\n\tcount?: number;\n}\n\n/** Menu */\nexport interface Menu {\n\tname: string;\n\tlabel: string;\n}\n\n/** Menu with items */\nexport interface MenuWithItems extends Menu {\n\titems: MenuItem[];\n}\n\n/** Menu item */\nexport interface MenuItem {\n\tid: string;\n\ttype: string;\n\tlabel: string;\n\tcustomUrl?: string;\n\treferenceCollection?: string;\n\treferenceId?: string;\n\ttarget?: string;\n\tparentId?: string | null;\n\tsortOrder: number;\n}\n\n/** Full schema export (returned by /api/schema) */\nexport interface SchemaExport {\n\tcollections: Array<{\n\t\tslug: string;\n\t\tlabel: string;\n\t\tlabelSingular: string;\n\t\tdescription?: string;\n\t\ticon?: string;\n\t\tsupports: string[];\n\t\tfields: Array<{\n\t\t\tslug: string;\n\t\t\tlabel: string;\n\t\t\ttype: string;\n\t\t\trequired: boolean;\n\t\t\tunique: boolean;\n\t\t\tdefaultValue?: unknown;\n\t\t\tvalidation?: unknown;\n\t\t\twidget?: string;\n\t\t\toptions?: unknown;\n\t\t}>;\n\t}>;\n\tversion: string;\n}\n\n/** Manifest — full schema + field descriptors */\nexport interface Manifest {\n\tversion: string;\n\thash: string;\n\tcollections: Record<\n\t\tstring,\n\t\t{\n\t\t\tlabel: string;\n\t\t\tlabelSingular: string;\n\t\t\tsupports: string[];\n\t\t\tfields: Record<string, { kind: string; label?: string; required?: boolean }>;\n\t\t}\n\t>;\n}\n\n// ---------------------------------------------------------------------------\n// Client errors\n// ---------------------------------------------------------------------------\n\nexport class EmDashApiError extends Error {\n\tconstructor(\n\t\tpublic readonly status: number,\n\t\tpublic readonly code: string,\n\t\tmessage: string,\n\t\tpublic readonly details?: Record<string, unknown>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"EmDashApiError\";\n\t}\n}\n\nexport class EmDashClientError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"EmDashClientError\";\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Client\n// ---------------------------------------------------------------------------\n\nexport class EmDashClient {\n\tprivate readonly baseUrl: string;\n\tprivate readonly transport: { fetch: (request: Request) => Promise<Response> };\n\n\t/** Cached field schemas per collection for PT conversion */\n\tprivate fieldSchemaCache = new Map<string, FieldSchema[]>();\n\n\tconstructor(options: EmDashClientOptions) {\n\t\tthis.baseUrl = options.baseUrl.replace(TRAILING_SLASH_PATTERN, \"\");\n\n\t\t// Build interceptor chain\n\t\tconst interceptors: Interceptor[] = [csrfInterceptor()];\n\n\t\tif (options.token) {\n\t\t\tinterceptors.push(tokenInterceptor(options.token));\n\t\t} else if (options.devBypass) {\n\t\t\tinterceptors.push(devBypassInterceptor(this.baseUrl));\n\t\t}\n\n\t\t// Auto-refresh expired OAuth tokens\n\t\tif (options.refreshToken) {\n\t\t\tinterceptors.push(\n\t\t\t\trefreshInterceptor({\n\t\t\t\t\trefreshToken: options.refreshToken,\n\t\t\t\t\ttokenEndpoint: `${this.baseUrl}/_emdash/api/oauth/token/refresh`,\n\t\t\t\t\tonTokenRefreshed: options.onTokenRefresh\n\t\t\t\t\t\t? (accessToken, _refreshToken, expiresAt) => {\n\t\t\t\t\t\t\t\tconst expiresIn = Math.floor((new Date(expiresAt).getTime() - Date.now()) / 1000);\n\t\t\t\t\t\t\t\toptions.onTokenRefresh!(accessToken, expiresIn);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tif (options.interceptors) {\n\t\t\tinterceptors.push(...options.interceptors);\n\t\t}\n\n\t\tthis.transport = createTransport({ interceptors });\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Schema\n\t// -----------------------------------------------------------------------\n\n\t/** List all collections */\n\tasync collections(): Promise<Collection[]> {\n\t\tconst data = await this.request<{ items: Collection[] }>(\"GET\", \"/schema/collections\");\n\t\treturn data.items;\n\t}\n\n\t/** Get a single collection with its fields */\n\tasync collection(slug: string): Promise<CollectionWithFields> {\n\t\tconst data = await this.request<{ item: CollectionWithFields }>(\n\t\t\t\"GET\",\n\t\t\t`/schema/collections/${encodeURIComponent(slug)}?includeFields=true`,\n\t\t);\n\t\tconst col = data.item;\n\t\t// Cache field schemas for PT conversion\n\t\tif (col.fields) {\n\t\t\tthis.fieldSchemaCache.set(\n\t\t\t\tslug,\n\t\t\t\tcol.fields.map((f) => ({ slug: f.slug, type: f.type })),\n\t\t\t);\n\t\t}\n\t\treturn col;\n\t}\n\n\t/** Create a collection */\n\tasync createCollection(input: {\n\t\tslug: string;\n\t\tlabel: string;\n\t\tlabelSingular?: string;\n\t\tdescription?: string;\n\t\ticon?: string;\n\t\tsupports?: string[];\n\t}): Promise<Collection> {\n\t\tconst data = await this.request<{ item: Collection }>(\"POST\", \"/schema/collections\", input);\n\t\treturn data.item;\n\t}\n\n\t/** Delete a collection */\n\tasync deleteCollection(slug: string): Promise<void> {\n\t\tawait this.request<unknown>(\"DELETE\", `/schema/collections/${encodeURIComponent(slug)}`);\n\t}\n\n\t/** Create a field on a collection */\n\tasync createField(\n\t\tcollection: string,\n\t\tinput: {\n\t\t\tslug: string;\n\t\t\ttype: string;\n\t\t\tlabel: string;\n\t\t\trequired?: boolean;\n\t\t\tunique?: boolean;\n\t\t\tdefaultValue?: unknown;\n\t\t\tvalidation?: unknown;\n\t\t\twidget?: string;\n\t\t\toptions?: unknown;\n\t\t\tsortOrder?: number;\n\t\t},\n\t): Promise<Field> {\n\t\tconst data = await this.request<{ item: Field }>(\n\t\t\t\"POST\",\n\t\t\t`/schema/collections/${encodeURIComponent(collection)}/fields`,\n\t\t\tinput,\n\t\t);\n\t\t// Invalidate field cache\n\t\tthis.fieldSchemaCache.delete(collection);\n\t\treturn data.item;\n\t}\n\n\t/** Delete a field from a collection */\n\tasync deleteField(collection: string, fieldSlug: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"DELETE\",\n\t\t\t`/schema/collections/${encodeURIComponent(collection)}/fields/${encodeURIComponent(fieldSlug)}`,\n\t\t);\n\t\tthis.fieldSchemaCache.delete(collection);\n\t}\n\n\t/** Get full manifest (schema + field descriptors + features) */\n\tasync manifest(): Promise<Manifest> {\n\t\treturn this.request<Manifest>(\"GET\", \"/manifest\");\n\t}\n\n\t/** Export full schema as JSON (used by `emdash types`) */\n\tasync schemaExport(): Promise<SchemaExport> {\n\t\treturn this.request<SchemaExport>(\"GET\", \"/schema\");\n\t}\n\n\t/** Export schema as TypeScript type definitions (used by `emdash types`) */\n\tasync schemaTypes(): Promise<string> {\n\t\tconst response = await this.requestRaw(\"GET\", \"/schema?format=typescript\");\n\t\tawait this.assertOk(response);\n\t\treturn response.text();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Content\n\t// -----------------------------------------------------------------------\n\n\t/** List content in a collection */\n\tasync list(\n\t\tcollection: string,\n\t\toptions?: {\n\t\t\tstatus?: string;\n\t\t\tlimit?: number;\n\t\t\tcursor?: string;\n\t\t\torderBy?: string;\n\t\t\torder?: \"asc\" | \"desc\";\n\t\t\tlocale?: string;\n\t\t},\n\t): Promise<ListResult<ContentItem>> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.status) params.set(\"status\", options.status);\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\t\tif (options?.cursor) params.set(\"cursor\", options.cursor);\n\t\tif (options?.orderBy) params.set(\"orderBy\", options.orderBy);\n\t\tif (options?.order) params.set(\"order\", options.order);\n\t\tif (options?.locale) params.set(\"locale\", options.locale);\n\n\t\tconst qs = params.toString();\n\t\tconst path = `/content/${encodeURIComponent(collection)}${qs ? `?${qs}` : \"\"}`;\n\t\treturn this.request<ListResult<ContentItem>>(\"GET\", path);\n\t}\n\n\t/** Async iterator that auto-follows cursors */\n\tasync *listAll(\n\t\tcollection: string,\n\t\toptions?: {\n\t\t\tstatus?: string;\n\t\t\tlimit?: number;\n\t\t\torderBy?: string;\n\t\t\torder?: \"asc\" | \"desc\";\n\t\t\tlocale?: string;\n\t\t},\n\t): AsyncGenerator<ContentItem> {\n\t\tlet cursor: string | undefined;\n\t\tdo {\n\t\t\tconst result = await this.list(collection, { ...options, cursor });\n\t\t\tfor (const item of result.items) {\n\t\t\t\tyield item;\n\t\t\t}\n\t\t\tcursor = result.nextCursor;\n\t\t} while (cursor);\n\t}\n\n\t/**\n\t * Get a single content item. Returns the item with a `_rev` token\n\t * that can be passed to update() for optimistic concurrency.\n\t */\n\tasync get(\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions?: { raw?: boolean; locale?: string },\n\t): Promise<ContentItem> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.locale) params.set(\"locale\", options.locale);\n\t\tconst qs = params.size > 0 ? `?${params}` : \"\";\n\t\tconst result = await this.requestRaw(\n\t\t\t\"GET\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}${qs}`,\n\t\t);\n\t\tif (!result.ok) {\n\t\t\tawait this.assertOk(result);\n\t\t}\n\n\t\tconst raw = (await result.json()) as { data: { item: ContentItem; _rev?: string } };\n\t\tconst json = raw.data;\n\t\tconst item = json.item;\n\n\t\t// Attach _rev to the item so callers can pass it back on update\n\t\tif (json._rev) {\n\t\t\titem._rev = json._rev;\n\t\t}\n\n\t\t// Convert PT fields to markdown unless raw is requested\n\t\tif (!options?.raw && item.data) {\n\t\t\tconst fields = await this.getFieldSchemas(collection);\n\t\t\titem.data = convertDataForRead(item.data, fields, false);\n\t\t}\n\n\t\treturn item;\n\t}\n\n\t/** Create a new content item */\n\tasync create(\n\t\tcollection: string,\n\t\tinput: {\n\t\t\tdata: Record<string, unknown>;\n\t\t\tslug?: string;\n\t\t\tstatus?: string;\n\t\t\tlocale?: string;\n\t\t\ttranslationOf?: string;\n\t\t},\n\t): Promise<ContentItem> {\n\t\t// Convert markdown strings to PT for portableText fields\n\t\tconst fields = await this.getFieldSchemas(collection);\n\t\tconst data = convertDataForWrite(input.data, fields);\n\n\t\tconst result = await this.request<{ item: ContentItem }>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}`,\n\t\t\t{ ...input, data },\n\t\t);\n\t\treturn result.item;\n\t}\n\n\t/**\n\t * Update a content item. Pass `_rev` from a prior get() for optimistic\n\t * concurrency — the server returns 409 if the item has changed.\n\t * Omit `_rev` for a blind write (no conflict detection).\n\t */\n\tasync update(\n\t\tcollection: string,\n\t\tid: string,\n\t\tinput: {\n\t\t\tdata?: Record<string, unknown>;\n\t\t\tslug?: string;\n\t\t\tstatus?: string;\n\t\t\t_rev?: string;\n\t\t},\n\t): Promise<ContentItem> {\n\t\t// Convert markdown strings to PT\n\t\tlet data = input.data;\n\t\tif (data) {\n\t\t\tconst fields = await this.getFieldSchemas(collection);\n\t\t\tdata = convertDataForWrite(data, fields);\n\t\t}\n\n\t\tconst body = {\n\t\t\tdata,\n\t\t\tslug: input.slug,\n\t\t\tstatus: input.status,\n\t\t\t...(input._rev ? { _rev: input._rev } : {}),\n\t\t};\n\t\tconst result = await this.request<{ item: ContentItem; _rev?: string }>(\n\t\t\t\"PUT\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,\n\t\t\tbody,\n\t\t);\n\n\t\tconst item = result.item;\n\t\tif (result._rev) {\n\t\t\titem._rev = result._rev;\n\t\t}\n\t\treturn item;\n\t}\n\n\t/** Delete (soft) a content item */\n\tasync delete(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"DELETE\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\t/** Publish a content item */\n\tasync publish(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/publish`,\n\t\t);\n\t}\n\n\t/** Unpublish a content item */\n\tasync unpublish(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/unpublish`,\n\t\t);\n\t}\n\n\t/** Schedule publishing */\n\tasync schedule(collection: string, id: string, options: { at: string }): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/schedule`,\n\t\t\t{ scheduledAt: options.at },\n\t\t);\n\t}\n\n\t/** Restore a trashed content item */\n\tasync restore(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/restore`,\n\t\t);\n\t}\n\n\t/** Compare live and draft revisions */\n\tasync compare(\n\t\tcollection: string,\n\t\tid: string,\n\t): Promise<{\n\t\thasChanges: boolean;\n\t\tlive: Record<string, unknown> | null;\n\t\tdraft: Record<string, unknown> | null;\n\t}> {\n\t\treturn this.request<{\n\t\t\thasChanges: boolean;\n\t\t\tlive: Record<string, unknown> | null;\n\t\t\tdraft: Record<string, unknown> | null;\n\t\t}>(\"GET\", `/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/compare`);\n\t}\n\n\t/** Discard draft revision, reverting to the published version */\n\tasync discardDraft(collection: string, id: string): Promise<void> {\n\t\tawait this.request<unknown>(\n\t\t\t\"POST\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/discard-draft`,\n\t\t);\n\t}\n\n\t/**\n\t * Get all translations of a content item.\n\t * Returns the translation group ID and a summary of each locale version.\n\t */\n\tasync translations(\n\t\tcollection: string,\n\t\tid: string,\n\t): Promise<{\n\t\ttranslationGroup: string;\n\t\ttranslations: Array<{\n\t\t\tid: string;\n\t\t\tlocale: string | null;\n\t\t\tslug: string | null;\n\t\t\tstatus: string;\n\t\t\tupdatedAt: string;\n\t\t}>;\n\t}> {\n\t\treturn this.request(\n\t\t\t\"GET\",\n\t\t\t`/content/${encodeURIComponent(collection)}/${encodeURIComponent(id)}/translations`,\n\t\t);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Media\n\t// -----------------------------------------------------------------------\n\n\t/** List media items */\n\tasync mediaList(options?: {\n\t\tmimeType?: string;\n\t\tlimit?: number;\n\t\tcursor?: string;\n\t}): Promise<ListResult<MediaItem>> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.mimeType) params.set(\"mimeType\", options.mimeType);\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\t\tif (options?.cursor) params.set(\"cursor\", options.cursor);\n\n\t\tconst qs = params.toString();\n\t\treturn this.request<ListResult<MediaItem>>(\"GET\", `/media${qs ? `?${qs}` : \"\"}`);\n\t}\n\n\t/** Get a single media item */\n\tasync mediaGet(id: string): Promise<MediaItem> {\n\t\tconst data = await this.request<{ item: MediaItem }>(\"GET\", `/media/${encodeURIComponent(id)}`);\n\t\treturn data.item;\n\t}\n\n\t/** Upload a media file */\n\tasync mediaUpload(\n\t\tfile: Uint8Array | Blob,\n\t\tfilename: string,\n\t\toptions?: { alt?: string; caption?: string; contentType?: string },\n\t): Promise<MediaItem> {\n\t\tconst formData = new FormData();\n\n\t\t// Handle different file types\n\t\tif (file instanceof Blob) {\n\t\t\tformData.append(\"file\", file, filename);\n\t\t} else {\n\t\t\tconst mimeType = options?.contentType ?? mimeFromFilename(filename);\n\t\t\tformData.append(\"file\", new Blob([file as BlobPart], { type: mimeType }), filename);\n\t\t}\n\n\t\tif (options?.alt) formData.append(\"alt\", options.alt);\n\t\tif (options?.caption) formData.append(\"caption\", options.caption);\n\n\t\tconst url = `${this.baseUrl}/_emdash/api/media`;\n\t\tconst request = new Request(url, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: formData,\n\t\t});\n\n\t\tconst response = await this.transport.fetch(request);\n\t\tawait this.assertOk(response);\n\n\t\tconst raw = (await response.json()) as { data: { item: MediaItem } };\n\t\treturn raw.data.item;\n\t}\n\n\t/** Delete a media item */\n\tasync mediaDelete(id: string): Promise<void> {\n\t\tawait this.request<unknown>(\"DELETE\", `/media/${encodeURIComponent(id)}`);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Search\n\t// -----------------------------------------------------------------------\n\n\t/** Full-text search */\n\tasync search(\n\t\tquery: string,\n\t\toptions?: { collection?: string; locale?: string; limit?: number },\n\t): Promise<SearchResult[]> {\n\t\tconst params = new URLSearchParams({ q: query });\n\t\tif (options?.collection) params.set(\"collections\", options.collection);\n\t\tif (options?.locale) params.set(\"locale\", options.locale);\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\n\t\tconst data = await this.request<{ items: SearchResult[] }>(\"GET\", `/search?${params}`);\n\t\treturn data.items;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Taxonomies\n\t// -----------------------------------------------------------------------\n\n\t/** List taxonomies */\n\tasync taxonomies(): Promise<Taxonomy[]> {\n\t\tconst data = await this.request<{ taxonomies: Taxonomy[] }>(\"GET\", \"/taxonomies\");\n\t\treturn data.taxonomies;\n\t}\n\n\t/** List terms in a taxonomy */\n\tasync terms(\n\t\ttaxonomy: string,\n\t\toptions?: { limit?: number; cursor?: string },\n\t): Promise<ListResult<Term>> {\n\t\tconst params = new URLSearchParams();\n\t\tif (options?.limit) params.set(\"limit\", String(options.limit));\n\t\tif (options?.cursor) params.set(\"cursor\", options.cursor);\n\n\t\tconst qs = params.toString();\n\t\treturn this.request<ListResult<Term>>(\n\t\t\t\"GET\",\n\t\t\t`/taxonomies/${encodeURIComponent(taxonomy)}/terms${qs ? `?${qs}` : \"\"}`,\n\t\t);\n\t}\n\n\t/** Create a taxonomy term */\n\tasync createTerm(\n\t\ttaxonomy: string,\n\t\tinput: { slug: string; label: string; parentId?: string; description?: string },\n\t): Promise<Term> {\n\t\treturn this.request<Term>(\"POST\", `/taxonomies/${encodeURIComponent(taxonomy)}/terms`, input);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Menus\n\t// -----------------------------------------------------------------------\n\n\t/** List menus */\n\tasync menus(): Promise<Menu[]> {\n\t\t// Handler returns a bare array, not { items: [...] }\n\t\treturn this.request<Menu[]>(\"GET\", \"/menus\");\n\t}\n\n\t/** Get a menu with its items */\n\tasync menu(name: string): Promise<MenuWithItems> {\n\t\treturn this.request<MenuWithItems>(\"GET\", `/menus/${encodeURIComponent(name)}`);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\t/** Make a typed JSON request to the API */\n\tprivate async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n\t\tconst response = await this.requestRaw(method, path, body);\n\t\tawait this.assertOk(response);\n\t\tconst json = (await response.json()) as { data: T };\n\t\treturn json.data;\n\t}\n\n\t/** Make a raw request — caller handles response */\n\tprivate async requestRaw(method: string, path: string, body?: unknown): Promise<Response> {\n\t\tconst url = `${this.baseUrl}/_emdash/api${path}`;\n\t\tconst headers: Record<string, string> = {\n\t\t\tAccept: \"application/json\",\n\t\t};\n\n\t\tlet requestBody: string | undefined;\n\t\tif (body !== undefined) {\n\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\trequestBody = JSON.stringify(body);\n\t\t}\n\n\t\tconst request = new Request(url, {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tbody: requestBody,\n\t\t});\n\n\t\treturn this.transport.fetch(request);\n\t}\n\n\t/** Assert a response is OK, throw typed error if not */\n\tprivate async assertOk(response: Response): Promise<void> {\n\t\tif (response.ok) return;\n\n\t\tlet code = \"UNKNOWN_ERROR\";\n\t\tlet message = `HTTP ${response.status}`;\n\t\tlet details: Record<string, unknown> | undefined;\n\n\t\ttry {\n\t\t\tconst json = (await response.json()) as {\n\t\t\t\terror?: { code?: string; message?: string; details?: Record<string, unknown> };\n\t\t\t};\n\t\t\tif (json.error) {\n\t\t\t\tcode = json.error.code ?? code;\n\t\t\t\tmessage = json.error.message ?? message;\n\t\t\t\tdetails = json.error.details;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Response body isn't JSON — use status text\n\t\t\tmessage = response.statusText || message;\n\t\t}\n\n\t\tthrow new EmDashApiError(response.status, code, message, details);\n\t}\n\n\t/** Get cached field schemas for a collection, fetching if needed */\n\tprivate async getFieldSchemas(collection: string): Promise<FieldSchema[]> {\n\t\tlet cached = this.fieldSchemaCache.get(collection);\n\t\tif (cached) return cached;\n\n\t\ttry {\n\t\t\tconst col = await this.collection(collection);\n\t\t\tcached = col.fields.map((f) => ({ slug: f.slug, type: f.type }));\n\t\t\tthis.fieldSchemaCache.set(collection, cached);\n\t\t\treturn cached;\n\t\t} catch {\n\t\t\t// If we can't fetch the schema, skip conversion\n\t\t\treturn [];\n\t\t}\n\t}\n}\n\n// Re-export transport types for interceptor authors\nexport type { Interceptor } from \"./transport.js\";\nexport {\n\tcreateTransport,\n\tcsrfInterceptor,\n\ttokenInterceptor,\n\tdevBypassInterceptor,\n} from \"./transport.js\";\nexport { portableTextToMarkdown, markdownToPortableText } from \"./portable-text.js\";\nexport type { PortableTextBlock } from \"./portable-text.js\";\n"],"mappings":"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"}
@@ -44,4 +44,4 @@ function getFallbackChain(locale) {
44
44
 
45
45
  //#endregion
46
46
  export { setI18nConfig as i, getI18nConfig as n, isI18nEnabled as r, getFallbackChain as t };
47
- //# sourceMappingURL=config-BXwuX8Bx.mjs.map
47
+ //# sourceMappingURL=config-CVssduLe.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-BXwuX8Bx.mjs","names":[],"sources":["../src/i18n/config.ts"],"sourcesContent":["/**\n * EmDash i18n Configuration\n *\n * Reads locale configuration from the virtual module (sourced from Astro config).\n * Initialized during runtime startup, then available via getI18nConfig().\n */\n\nexport interface I18nConfig {\n\tdefaultLocale: string;\n\tlocales: string[];\n\tfallback?: Record<string, string>;\n\tprefixDefaultLocale?: boolean;\n}\n\nlet _config: I18nConfig | null | undefined;\n\n/**\n * Initialize i18n config from virtual module data.\n * Called during runtime initialization.\n */\nexport function setI18nConfig(config: I18nConfig | null): void {\n\t_config = config;\n}\n\n/**\n * Get the current i18n config.\n * Returns null if i18n is not configured.\n */\nexport function getI18nConfig(): I18nConfig | null {\n\treturn _config ?? null;\n}\n\n/**\n * Check if i18n is enabled.\n * Returns true when multiple locales are configured.\n */\nexport function isI18nEnabled(): boolean {\n\treturn _config != null && _config.locales.length > 1;\n}\n\n/**\n * Resolve fallback locale chain for a given locale.\n * Returns array of locales to try, from most preferred to least.\n * Always ends with defaultLocale.\n */\nexport function getFallbackChain(locale: string): string[] {\n\tif (!_config) return [locale];\n\n\tconst chain: string[] = [locale];\n\tlet current = locale;\n\tconst visited = new Set<string>([locale]);\n\n\twhile (_config.fallback?.[current]) {\n\t\t// eslint-disable-next-line typescript-eslint(no-unnecessary-type-assertion) -- noUncheckedIndexedAccess\n\t\tconst next = _config.fallback[current]!;\n\t\tif (visited.has(next)) break; // prevent cycles\n\t\tchain.push(next);\n\t\tvisited.add(next);\n\t\tcurrent = next;\n\t}\n\n\t// Always end with defaultLocale if not already in chain\n\tif (!visited.has(_config.defaultLocale)) {\n\t\tchain.push(_config.defaultLocale);\n\t}\n\n\treturn chain;\n}\n"],"mappings":";AAcA,IAAI;;;;;AAMJ,SAAgB,cAAc,QAAiC;AAC9D,WAAU;;;;;;AAOX,SAAgB,gBAAmC;AAClD,QAAO,WAAW;;;;;;AAOnB,SAAgB,gBAAyB;AACxC,QAAO,WAAW,QAAQ,QAAQ,QAAQ,SAAS;;;;;;;AAQpD,SAAgB,iBAAiB,QAA0B;AAC1D,KAAI,CAAC,QAAS,QAAO,CAAC,OAAO;CAE7B,MAAM,QAAkB,CAAC,OAAO;CAChC,IAAI,UAAU;CACd,MAAM,UAAU,IAAI,IAAY,CAAC,OAAO,CAAC;AAEzC,QAAO,QAAQ,WAAW,UAAU;EAEnC,MAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,QAAM,KAAK,KAAK;AAChB,UAAQ,IAAI,KAAK;AACjB,YAAU;;AAIX,KAAI,CAAC,QAAQ,IAAI,QAAQ,cAAc,CACtC,OAAM,KAAK,QAAQ,cAAc;AAGlC,QAAO"}
1
+ {"version":3,"file":"config-CVssduLe.mjs","names":[],"sources":["../src/i18n/config.ts"],"sourcesContent":["/**\n * EmDash i18n Configuration\n *\n * Reads locale configuration from the virtual module (sourced from Astro config).\n * Initialized during runtime startup, then available via getI18nConfig().\n */\n\nexport interface I18nConfig {\n\tdefaultLocale: string;\n\tlocales: string[];\n\tfallback?: Record<string, string>;\n\tprefixDefaultLocale?: boolean;\n}\n\nlet _config: I18nConfig | null | undefined;\n\n/**\n * Initialize i18n config from virtual module data.\n * Called during runtime initialization.\n */\nexport function setI18nConfig(config: I18nConfig | null): void {\n\t_config = config;\n}\n\n/**\n * Get the current i18n config.\n * Returns null if i18n is not configured.\n */\nexport function getI18nConfig(): I18nConfig | null {\n\treturn _config ?? null;\n}\n\n/**\n * Check if i18n is enabled.\n * Returns true when multiple locales are configured.\n */\nexport function isI18nEnabled(): boolean {\n\treturn _config != null && _config.locales.length > 1;\n}\n\n/**\n * Resolve fallback locale chain for a given locale.\n * Returns array of locales to try, from most preferred to least.\n * Always ends with defaultLocale.\n */\nexport function getFallbackChain(locale: string): string[] {\n\tif (!_config) return [locale];\n\n\tconst chain: string[] = [locale];\n\tlet current = locale;\n\tconst visited = new Set<string>([locale]);\n\n\twhile (_config.fallback?.[current]) {\n\t\t// eslint-disable-next-line typescript-eslint(no-unnecessary-type-assertion) -- noUncheckedIndexedAccess\n\t\tconst next = _config.fallback[current]!;\n\t\tif (visited.has(next)) break; // prevent cycles\n\t\tchain.push(next);\n\t\tvisited.add(next);\n\t\tcurrent = next;\n\t}\n\n\t// Always end with defaultLocale if not already in chain\n\tif (!visited.has(_config.defaultLocale)) {\n\t\tchain.push(_config.defaultLocale);\n\t}\n\n\treturn chain;\n}\n"],"mappings":";AAcA,IAAI;;;;;AAMJ,SAAgB,cAAc,QAAiC;AAC9D,WAAU;;;;;;AAOX,SAAgB,gBAAmC;AAClD,QAAO,WAAW;;;;;;AAOnB,SAAgB,gBAAyB;AACxC,QAAO,WAAW,QAAQ,QAAQ,QAAQ,SAAS;;;;;;;AAQpD,SAAgB,iBAAiB,QAA0B;AAC1D,KAAI,CAAC,QAAS,QAAO,CAAC,OAAO;CAE7B,MAAM,QAAkB,CAAC,OAAO;CAChC,IAAI,UAAU;CACd,MAAM,UAAU,IAAI,IAAY,CAAC,OAAO,CAAC;AAEzC,QAAO,QAAQ,WAAW,UAAU;EAEnC,MAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,QAAM,KAAK,KAAK;AAChB,UAAQ,IAAI,KAAK;AACjB,YAAU;;AAIX,KAAI,CAAC,QAAQ,IAAI,QAAQ,cAAc,CACtC,OAAM,KAAK,QAAQ,cAAc;AAGlC,QAAO"}
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
1
+ import { i as __exportAll } from "./runner-DMnlIkh4.mjs";
2
2
  import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
3
3
  import { i as encodeCursor, r as decodeCursor, t as EmDashValidationError } from "./types-BIgulNsW.mjs";
4
4
  import { sql } from "kysely";
@@ -504,11 +504,22 @@ var ContentRepository = class {
504
504
  /**
505
505
  * Permanently delete content (cannot be undone)
506
506
  */
507
+ /**
508
+ * Permanently delete a soft-deleted content row.
509
+ *
510
+ * Returns `true` only when a soft-deleted (trashed) row was removed.
511
+ * Returns `false` when no row exists OR when the row exists but is live —
512
+ * the caller is responsible for distinguishing these cases (typically via
513
+ * a follow-up `findByIdOrSlugIncludingTrashed` to surface NOT_FOUND vs
514
+ * NOT_TRASHED). The `AND deleted_at IS NOT NULL` clause is the safety net
515
+ * that prevents permanent delete from bypassing the trash workflow.
516
+ */
507
517
  async permanentDelete(type, id) {
508
518
  const tableName = getTableName(type);
509
519
  return ((await sql`
510
520
  DELETE FROM ${sql.ref(tableName)}
511
521
  WHERE id = ${id}
522
+ AND deleted_at IS NOT NULL
512
523
  `.execute(this.db)).numAffectedRows ?? 0n) > 0n;
513
524
  }
514
525
  /**
@@ -667,8 +678,14 @@ var ContentRepository = class {
667
678
  * Syncs the draft revision's data into the content table columns so the
668
679
  * content table always reflects the published version.
669
680
  * If no draft revision exists, creates one from current data and publishes it.
681
+ *
682
+ * `publishedAt` (optional) overrides the publication timestamp. If omitted,
683
+ * the existing `published_at` is preserved (idempotent re-publish keeps the
684
+ * original date) and falls back to the current time on first publish. Pass
685
+ * an explicit value to backdate a publish (e.g. when migrating content from
686
+ * another CMS).
670
687
  */
671
- async publish(type, id) {
688
+ async publish(type, id, publishedAt) {
672
689
  const tableName = getTableName(type);
673
690
  const now = (/* @__PURE__ */ new Date()).toISOString();
674
691
  const existing = await this.findById(type, id);
@@ -689,17 +706,28 @@ var ContentRepository = class {
689
706
  WHERE id = ${id}
690
707
  `.execute(this.db);
691
708
  }
692
- await sql`
693
- UPDATE ${sql.ref(tableName)}
694
- SET live_revision_id = ${revisionToPublish},
695
- draft_revision_id = NULL,
696
- status = 'published',
697
- scheduled_at = NULL,
698
- published_at = COALESCE(published_at, ${now}),
699
- updated_at = ${now}
700
- WHERE id = ${id}
701
- AND deleted_at IS NULL
702
- `.execute(this.db);
709
+ if (publishedAt !== void 0) await sql`
710
+ UPDATE ${sql.ref(tableName)}
711
+ SET live_revision_id = ${revisionToPublish},
712
+ draft_revision_id = NULL,
713
+ status = 'published',
714
+ scheduled_at = NULL,
715
+ published_at = ${publishedAt},
716
+ updated_at = ${now}
717
+ WHERE id = ${id}
718
+ AND deleted_at IS NULL
719
+ `.execute(this.db);
720
+ else await sql`
721
+ UPDATE ${sql.ref(tableName)}
722
+ SET live_revision_id = ${revisionToPublish},
723
+ draft_revision_id = NULL,
724
+ status = 'published',
725
+ scheduled_at = NULL,
726
+ published_at = COALESCE(published_at, ${now}),
727
+ updated_at = ${now}
728
+ WHERE id = ${id}
729
+ AND deleted_at IS NULL
730
+ `.execute(this.db);
703
731
  const updated = await this.findById(type, id);
704
732
  if (!updated) throw new Error("Content not found");
705
733
  return updated;
@@ -875,4 +903,4 @@ var ContentRepository = class {
875
903
 
876
904
  //#endregion
877
905
  export { slugify as a, decodeSlug as i, content_exports as n, RevisionRepository as r, ContentRepository as t };
878
- //# sourceMappingURL=content-BcQPYxdV.mjs.map
906
+ //# sourceMappingURL=content-C7G4QXkK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-C7G4QXkK.mjs","names":[],"sources":["../src/utils/slugify.ts","../src/database/repositories/revision.ts","../src/database/repositories/content.ts"],"sourcesContent":["// Regex patterns for slug normalization\nconst DIACRITICS_PATTERN = /[\\u0300-\\u036f]/g;\nconst WHITESPACE_UNDERSCORE_PATTERN = /[\\s_]+/g;\nconst NON_ALPHANUMERIC_HYPHEN_PATTERN = /[^a-z0-9-]/g;\nconst MULTIPLE_HYPHENS_PATTERN = /-+/g;\nconst LEADING_TRAILING_HYPHEN_PATTERN = /^-|-$/g;\nconst TRAILING_HYPHEN_PATTERN = /-$/;\n\n/**\n * Convert a string to a URL-friendly slug.\n *\n * Handles unicode by normalizing to NFD and stripping diacritics,\n * so \"café\" becomes \"cafe\", \"naïve\" becomes \"naive\", etc.\n */\n/**\n * Decode a URI-encoded slug parameter.\n *\n * Browsers percent-encode non-ASCII characters in URLs, so a slug like\n * \"మేష-రాసి\" arrives as \"%e0%b0%ae%e0%b1%87%e0%b0%b7-%e0%b0%b0%e0%b0%be%e0%b0%b8%e0%b0%bf\".\n * Call this on `Astro.params.slug` before using it in database lookups.\n */\nexport function decodeSlug(raw: string | undefined): string | undefined {\n\treturn raw ? decodeURIComponent(raw) : undefined;\n}\n\nexport function slugify(text: string, maxLength: number = 80): string {\n\treturn (\n\t\ttext\n\t\t\t.toLowerCase()\n\t\t\t.normalize(\"NFD\")\n\t\t\t.replace(DIACRITICS_PATTERN, \"\")\n\t\t\t.replace(WHITESPACE_UNDERSCORE_PATTERN, \"-\")\n\t\t\t.replace(NON_ALPHANUMERIC_HYPHEN_PATTERN, \"\")\n\t\t\t.replace(MULTIPLE_HYPHENS_PATTERN, \"-\")\n\t\t\t.replace(LEADING_TRAILING_HYPHEN_PATTERN, \"\")\n\t\t\t.slice(0, maxLength)\n\t\t\t// Clean trailing hyphen from truncation\n\t\t\t.replace(TRAILING_HYPHEN_PATTERN, \"\")\n\t);\n}\n","import type { Kysely } from \"kysely\";\nimport { monotonicFactory } from \"ulidx\";\n\nimport type { Database, RevisionTable } from \"../types.js\";\n\nconst monotonic = monotonicFactory();\n\nexport interface Revision {\n\tid: string;\n\tcollection: string;\n\tentryId: string;\n\tdata: Record<string, unknown>;\n\tauthorId: string | null;\n\tcreatedAt: string;\n}\n\nexport interface CreateRevisionInput {\n\tcollection: string;\n\tentryId: string;\n\tdata: Record<string, unknown>;\n\tauthorId?: string;\n}\n\n/**\n * Revision repository for version history\n *\n * Each revision stores a JSON snapshot of the content at a point in time.\n * Used when collection has `supports: [\"revisions\"]` enabled.\n */\nexport class RevisionRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new revision\n\t */\n\tasync create(input: CreateRevisionInput): Promise<Revision> {\n\t\tconst id = monotonic();\n\n\t\tconst row: Omit<RevisionTable, \"created_at\"> = {\n\t\t\tid,\n\t\t\tcollection: input.collection,\n\t\t\tentry_id: input.entryId,\n\t\t\tdata: JSON.stringify(input.data),\n\t\t\tauthor_id: input.authorId ?? null,\n\t\t};\n\n\t\tawait this.db.insertInto(\"revisions\").values(row).execute();\n\n\t\tconst revision = await this.findById(id);\n\t\tif (!revision) {\n\t\t\tthrow new Error(\"Failed to create revision\");\n\t\t}\n\t\treturn revision;\n\t}\n\n\t/**\n\t * Find revision by ID\n\t */\n\tasync findById(id: string): Promise<Revision | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToRevision(row) : null;\n\t}\n\n\t/**\n\t * Get all revisions for an entry (newest first)\n\t *\n\t * Orders by monotonic ULID (descending). The monotonic factory\n\t * guarantees strictly increasing IDs even within the same millisecond.\n\t */\n\tasync findByEntry(\n\t\tcollection: string,\n\t\tentryId: string,\n\t\toptions: { limit?: number } = {},\n\t): Promise<Revision[]> {\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.orderBy(\"id\", \"desc\");\n\n\t\tif (options.limit) {\n\t\t\tquery = query.limit(options.limit);\n\t\t}\n\n\t\tconst rows = await query.execute();\n\t\treturn rows.map((row) => this.rowToRevision(row));\n\t}\n\n\t/**\n\t * Get the most recent revision for an entry\n\t */\n\tasync findLatest(collection: string, entryId: string): Promise<Revision | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(1)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToRevision(row) : null;\n\t}\n\n\t/**\n\t * Count revisions for an entry\n\t */\n\tasync countByEntry(collection: string, entryId: string): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Delete all revisions for an entry (use when entry is deleted)\n\t */\n\tasync deleteByEntry(collection: string, entryId: string): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"revisions\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Delete old revisions, keeping the most recent N\n\t */\n\tasync pruneOldRevisions(collection: string, entryId: string, keepCount: number): Promise<number> {\n\t\t// Get IDs of revisions to keep\n\t\tconst keep = await this.db\n\t\t\t.selectFrom(\"revisions\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\") // ULID tiebreaker\n\t\t\t.limit(keepCount)\n\t\t\t.execute();\n\n\t\tconst keepIds = keep.map((r) => r.id);\n\n\t\tif (keepIds.length === 0) return 0;\n\n\t\t// Delete everything else for this entry\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"revisions\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"entry_id\", \"=\", entryId)\n\t\t\t.where(\"id\", \"not in\", keepIds)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Update revision data in place\n\t * Used for autosave to avoid creating many small revisions.\n\t */\n\tasync updateData(id: string, data: Record<string, unknown>): Promise<void> {\n\t\tawait this.db\n\t\t\t.updateTable(\"revisions\")\n\t\t\t.set({ data: JSON.stringify(data) })\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Convert database row to Revision object\n\t */\n\tprivate rowToRevision(row: {\n\t\tid: string;\n\t\tcollection: string;\n\t\tentry_id: string;\n\t\tdata: string;\n\t\tauthor_id: string | null;\n\t\tcreated_at: string;\n\t}): Revision {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tcollection: row.collection,\n\t\t\tentryId: row.entry_id,\n\t\t\tdata: JSON.parse(row.data),\n\t\t\tauthorId: row.author_id,\n\t\t\tcreatedAt: row.created_at,\n\t\t};\n\t}\n}\n","import { sql, type Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { slugify } from \"../../utils/slugify.js\";\nimport type { Database } from \"../types.js\";\nimport { validateIdentifier } from \"../validate.js\";\nimport { RevisionRepository } from \"./revision.js\";\nimport type {\n\tCreateContentInput,\n\tUpdateContentInput,\n\tFindManyOptions,\n\tFindManyResult,\n\tContentItem,\n} from \"./types.js\";\nimport { EmDashValidationError, encodeCursor, decodeCursor } from \"./types.js\";\n\n// Regex pattern for ULID validation\nconst ULID_PATTERN = /^[0-9A-Z]{26}$/;\n\n/**\n * System columns that exist in every ec_* table\n */\nconst SYSTEM_COLUMNS = new Set([\n\t\"id\",\n\t\"slug\",\n\t\"status\",\n\t\"author_id\",\n\t\"primary_byline_id\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t\"locale\",\n\t\"translation_group\",\n]);\n\n/**\n * Get the table name for a collection type\n */\nfunction getTableName(type: string): string {\n\tvalidateIdentifier(type, \"collection type\");\n\treturn `ec_${type}`;\n}\n\n/**\n * Serialize a value for database storage\n * Objects/arrays are JSON-stringified\n * Booleans are converted to 0/1 for SQLite\n */\nfunction serializeValue(value: unknown): unknown {\n\tif (value === null || value === undefined) {\n\t\treturn null;\n\t}\n\tif (typeof value === \"boolean\") {\n\t\treturn value ? 1 : 0;\n\t}\n\tif (typeof value === \"object\") {\n\t\treturn JSON.stringify(value);\n\t}\n\treturn value;\n}\n\n/**\n * Deserialize a value from database storage\n * Attempts to parse JSON strings that look like objects/arrays\n */\nfunction deserializeValue(value: unknown): unknown {\n\tif (typeof value === \"string\") {\n\t\t// Try to parse if it looks like JSON\n\t\tif (value.startsWith(\"{\") || value.startsWith(\"[\")) {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(value);\n\t\t\t} catch {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t}\n\treturn value;\n}\n\n/** Pattern for escaping special regex characters */\nconst REGEX_ESCAPE_PATTERN = /[.*+?^${}()|[\\]\\\\]/g;\n\n/**\n * Escape special regex characters in a string for use in `new RegExp()`\n */\nfunction escapeRegExp(s: string): string {\n\treturn s.replace(REGEX_ESCAPE_PATTERN, \"\\\\$&\");\n}\n\n/**\n * Repository for content CRUD operations\n *\n * Content is stored in per-collection tables (ec_posts, ec_pages, etc.)\n * Each field becomes a real column in the table.\n */\nexport class ContentRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new content item\n\t */\n\tasync create(input: CreateContentInput): Promise<ContentItem> {\n\t\tconst id = ulid();\n\t\tconst now = new Date().toISOString();\n\n\t\tconst {\n\t\t\ttype,\n\t\t\tslug,\n\t\t\tdata,\n\t\t\tstatus = \"draft\",\n\t\t\tauthorId,\n\t\t\tprimaryBylineId,\n\t\t\tlocale,\n\t\t\ttranslationOf,\n\t\t\tpublishedAt,\n\t\t\tcreatedAt,\n\t\t} = input;\n\n\t\t// Validate required fields\n\t\tif (!type) {\n\t\t\tthrow new EmDashValidationError(\"Content type is required\");\n\t\t}\n\n\t\tconst tableName = getTableName(type);\n\n\t\t// Resolve translation_group: if translationOf is set, look up the source item's group\n\t\tlet translationGroup: string = id; // default: self-reference\n\t\tif (translationOf) {\n\t\t\tconst source = await this.findById(type, translationOf);\n\t\t\tif (!source) {\n\t\t\t\tthrow new EmDashValidationError(\"Translation source content not found\");\n\t\t\t}\n\t\t\ttranslationGroup = source.translationGroup || source.id;\n\t\t}\n\n\t\t// Build column names and values\n\t\tconst columns: string[] = [\n\t\t\t\"id\",\n\t\t\t\"slug\",\n\t\t\t\"status\",\n\t\t\t\"author_id\",\n\t\t\t\"primary_byline_id\",\n\t\t\t\"created_at\",\n\t\t\t\"updated_at\",\n\t\t\t\"published_at\",\n\t\t\t\"version\",\n\t\t\t\"locale\",\n\t\t\t\"translation_group\",\n\t\t];\n\t\tconst values: unknown[] = [\n\t\t\tid,\n\t\t\tslug || null,\n\t\t\tstatus,\n\t\t\tauthorId || null,\n\t\t\tprimaryBylineId ?? null,\n\t\t\tcreatedAt || now,\n\t\t\tnow,\n\t\t\tpublishedAt || null,\n\t\t\t1,\n\t\t\tlocale || \"en\",\n\t\t\ttranslationGroup,\n\t\t];\n\n\t\t// Add data fields as columns (skip system columns to prevent injection via data)\n\t\tif (data && typeof data === \"object\") {\n\t\t\tfor (const [key, value] of Object.entries(data)) {\n\t\t\t\tif (!SYSTEM_COLUMNS.has(key)) {\n\t\t\t\t\tvalidateIdentifier(key, \"content field name\");\n\t\t\t\t\tcolumns.push(key);\n\t\t\t\t\tvalues.push(serializeValue(value));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Build dynamic INSERT using raw SQL\n\t\tconst columnRefs = columns.map((c) => sql.ref(c));\n\t\tconst valuePlaceholders = values.map((v) => (v === null ? sql`NULL` : sql`${v}`));\n\n\t\tawait sql`\n\t\t\tINSERT INTO ${sql.ref(tableName)} (${sql.join(columnRefs, sql`, `)})\n\t\t\tVALUES (${sql.join(valuePlaceholders, sql`, `)})\n\t\t`.execute(this.db);\n\n\t\t// Fetch and return the created item\n\t\tconst item = await this.findById(type, id);\n\t\tif (!item) {\n\t\t\tthrow new Error(\"Failed to create content\");\n\t\t}\n\t\treturn item;\n\t}\n\n\t/**\n\t * Generate a unique slug for a content item within a collection.\n\t *\n\t * Checks the collection table for existing slugs that match `baseSlug`\n\t * (optionally scoped to a locale) and appends a numeric suffix (`-1`,\n\t * `-2`, etc.) on collision to guarantee uniqueness.\n\t *\n\t * Returns `null` if `baseSlug` is empty after slugification.\n\t */\n\tasync generateUniqueSlug(type: string, text: string, locale?: string): Promise<string | null> {\n\t\tconst baseSlug = slugify(text);\n\t\tif (!baseSlug) return null;\n\n\t\tconst tableName = getTableName(type);\n\n\t\t// Check if the base slug is available\n\t\tconst existing = locale\n\t\t\t? await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${baseSlug}\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${baseSlug}\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db);\n\n\t\tif (existing.rows.length === 0) {\n\t\t\treturn baseSlug;\n\t\t}\n\n\t\t// Find all slugs matching the pattern `baseSlug` or `baseSlug-N`\n\t\tconst pattern = `${baseSlug}-%`;\n\t\tconst candidates = locale\n\t\t\t? await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE (slug = ${baseSlug} OR slug LIKE ${pattern})\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<{ slug: string }>`\n\t\t\t\t\tSELECT slug FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${baseSlug} OR slug LIKE ${pattern}\n\t\t\t\t`.execute(this.db);\n\n\t\t// Find the highest numeric suffix in use\n\t\tlet maxSuffix = 0;\n\t\tconst suffixPattern = new RegExp(`^${escapeRegExp(baseSlug)}-(\\\\d+)$`);\n\t\tfor (const row of candidates.rows) {\n\t\t\tconst match = suffixPattern.exec(row.slug);\n\t\t\tif (match) {\n\t\t\t\tconst n = parseInt(match[1], 10);\n\t\t\t\tif (n > maxSuffix) maxSuffix = n;\n\t\t\t}\n\t\t}\n\n\t\treturn `${baseSlug}-${maxSuffix + 1}`;\n\t}\n\n\t/**\n\t * Duplicate a content item\n\t * Creates a new draft copy with \"(Copy)\" appended to the title.\n\t * A slug is auto-generated from the new title by the handler layer.\n\t */\n\tasync duplicate(type: string, id: string, authorId?: string): Promise<ContentItem> {\n\t\t// Fetch the original item\n\t\tconst original = await this.findById(type, id);\n\t\tif (!original) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// Prepare the new data\n\t\tconst newData = { ...original.data };\n\n\t\t// Append \"(Copy)\" to title if present\n\t\tif (typeof newData.title === \"string\") {\n\t\t\tnewData.title = `${newData.title} (Copy)`;\n\t\t} else if (typeof newData.name === \"string\") {\n\t\t\tnewData.name = `${newData.name} (Copy)`;\n\t\t}\n\n\t\t// Auto-generate a unique slug from the new title/name\n\t\tconst slugSource =\n\t\t\ttypeof newData.title === \"string\"\n\t\t\t\t? newData.title\n\t\t\t\t: typeof newData.name === \"string\"\n\t\t\t\t\t? newData.name\n\t\t\t\t\t: null;\n\n\t\tconst slug = slugSource\n\t\t\t? await this.generateUniqueSlug(type, slugSource, original.locale ?? undefined)\n\t\t\t: null;\n\n\t\t// Create the duplicate as a draft — use override authorId if provided (caller owns the copy)\n\t\treturn this.create({\n\t\t\ttype,\n\t\t\tslug,\n\t\t\tdata: newData,\n\t\t\tstatus: \"draft\",\n\t\t\tauthorId: authorId || original.authorId || undefined,\n\t\t});\n\t}\n\n\t/**\n\t * Find content by ID\n\t */\n\tasync findById(type: string, id: string): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find content by id, including trashed (soft-deleted) items.\n\t * Used by restore endpoint for ownership checks.\n\t */\n\tasync findByIdIncludingTrashed(type: string, id: string): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE id = ${id}\n\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find content by ID or slug. Tries ID first if it looks like a ULID,\n\t * otherwise tries slug. Falls back to the other if the first lookup misses.\n\t */\n\tasync findByIdOrSlug(\n\t\ttype: string,\n\t\tidentifier: string,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\treturn this._findByIdOrSlug(type, identifier, false, locale);\n\t}\n\n\t/**\n\t * Find content by ID or slug, including trashed (soft-deleted) items.\n\t * Used by restore/permanent-delete endpoints.\n\t */\n\tasync findByIdOrSlugIncludingTrashed(\n\t\ttype: string,\n\t\tidentifier: string,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\treturn this._findByIdOrSlug(type, identifier, true, locale);\n\t}\n\n\tprivate async _findByIdOrSlug(\n\t\ttype: string,\n\t\tidentifier: string,\n\t\tincludeTrashed: boolean,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\t// ULIDs are 26 uppercase alphanumeric chars\n\t\tconst looksLikeUlid = ULID_PATTERN.test(identifier);\n\n\t\tconst findById = includeTrashed\n\t\t\t? (t: string, id: string) => this.findByIdIncludingTrashed(t, id)\n\t\t\t: (t: string, id: string) => this.findById(t, id);\n\t\tconst findBySlug = includeTrashed\n\t\t\t? (t: string, s: string) => this.findBySlugIncludingTrashed(t, s, locale)\n\t\t\t: (t: string, s: string) => this.findBySlug(t, s, locale);\n\n\t\tif (looksLikeUlid) {\n\t\t\t// Try ID first, fall back to slug\n\t\t\tconst byId = await findById(type, identifier);\n\t\t\tif (byId) return byId;\n\t\t\treturn findBySlug(type, identifier);\n\t\t}\n\t\t// Try slug first, fall back to ID\n\t\tconst bySlug = await findBySlug(type, identifier);\n\t\tif (bySlug) return bySlug;\n\t\treturn findById(type, identifier);\n\t}\n\n\t/**\n\t * Find content by slug\n\t */\n\tasync findBySlug(type: string, slug: string, locale?: string): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = locale\n\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t\tAND deleted_at IS NULL\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tAND deleted_at IS NULL\n\t\t\t\t\tORDER BY locale ASC\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find content by slug, including trashed (soft-deleted) items.\n\t * Used by restore/permanent-delete endpoints.\n\t */\n\tasync findBySlugIncludingTrashed(\n\t\ttype: string,\n\t\tslug: string,\n\t\tlocale?: string,\n\t): Promise<ContentItem | null> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = locale\n\t\t\t? await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tAND locale = ${locale}\n\t\t\t\t`.execute(this.db)\n\t\t\t: await sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\t\t\tWHERE slug = ${slug}\n\t\t\t\t\tORDER BY locale ASC\n\t\t\t\t\tLIMIT 1\n\t\t\t\t`.execute(this.db);\n\n\t\tconst row = result.rows[0];\n\t\tif (!row) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn this.mapRow(type, row);\n\t}\n\n\t/**\n\t * Find many content items with filtering and pagination\n\t */\n\tasync findMany(\n\t\ttype: string,\n\t\toptions: FindManyOptions = {},\n\t): Promise<FindManyResult<ContentItem>> {\n\t\tconst tableName = getTableName(type);\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\t// Determine ordering\n\t\tconst orderField = options.orderBy?.field || \"createdAt\";\n\t\tconst orderDirection = options.orderBy?.direction || \"desc\";\n\t\tconst dbField = this.mapOrderField(orderField);\n\n\t\t// Validate order direction to prevent injection\n\t\tconst safeOrderDirection = orderDirection.toLowerCase() === \"asc\" ? \"ASC\" : \"DESC\";\n\n\t\t// Build query with parameterized values (no string interpolation)\n\t\t// Note: Dynamic content tables have deleted_at column, cast needed for Kysely\n\t\tlet query = this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.selectAll()\n\t\t\t.where(\"deleted_at\" as never, \"is\", null);\n\n\t\t// Apply filters with parameterized queries\n\t\tif (options.where?.status) {\n\t\t\tquery = query.where(\"status\", \"=\", options.where.status);\n\t\t}\n\n\t\tif (options.where?.authorId) {\n\t\t\tquery = query.where(\"author_id\", \"=\", options.where.authorId);\n\t\t}\n\n\t\tif (options.where?.locale) {\n\t\t\tquery = query.where(\"locale\" as any, \"=\", options.where.locale);\n\t\t}\n\n\t\t// Handle cursor pagination — decodeCursor throws InvalidCursorError\n\t\t// on malformed input; let it propagate so handlers surface a\n\t\t// structured INVALID_CURSOR rather than silently returning page 1.\n\t\tif (options.cursor) {\n\t\t\tconst { orderValue, id: cursorId } = decodeCursor(options.cursor);\n\n\t\t\tif (safeOrderDirection === \"DESC\") {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \"<\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \">\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \">\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Apply ordering and limit\n\t\tquery = query\n\t\t\t.orderBy(dbField as any, safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.orderBy(\"id\", safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit);\n\n\t\tconst mappedResult: FindManyResult<ContentItem> = {\n\t\t\titems: items.map((row) => this.mapRow(type, row as Record<string, unknown>)),\n\t\t};\n\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst lastRow = items.at(-1) as Record<string, unknown>;\n\t\t\tconst lastOrderValue = lastRow[dbField];\n\t\t\tconst orderStr =\n\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t: \"\";\n\t\t\tmappedResult.nextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t}\n\n\t\treturn mappedResult;\n\t}\n\n\t/**\n\t * Update content\n\t */\n\tasync update(type: string, id: string, input: UpdateContentInput): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\t// Build update object with parameterized values\n\t\tconst updates: Record<string, unknown> = {\n\t\t\tupdated_at: now,\n\t\t\tversion: sql`version + 1`,\n\t\t};\n\n\t\tif (input.status !== undefined) {\n\t\t\tupdates.status = input.status;\n\t\t}\n\n\t\tif (input.slug !== undefined) {\n\t\t\tupdates.slug = input.slug;\n\t\t}\n\n\t\tif (input.publishedAt !== undefined) {\n\t\t\tupdates.published_at = input.publishedAt;\n\t\t}\n\n\t\tif (input.scheduledAt !== undefined) {\n\t\t\tupdates.scheduled_at = input.scheduledAt;\n\t\t}\n\n\t\tif (input.authorId !== undefined) {\n\t\t\tupdates.author_id = input.authorId;\n\t\t}\n\n\t\tif (input.primaryBylineId !== undefined) {\n\t\t\tupdates.primary_byline_id = input.primaryBylineId;\n\t\t}\n\n\t\t// Update data fields (skip system columns to prevent injection via data)\n\t\tif (input.data !== undefined && typeof input.data === \"object\") {\n\t\t\tfor (const [key, value] of Object.entries(input.data)) {\n\t\t\t\tif (!SYSTEM_COLUMNS.has(key)) {\n\t\t\t\t\tvalidateIdentifier(key, \"content field name\");\n\t\t\t\t\tupdates[key] = serializeValue(value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.db\n\t\t\t.updateTable(tableName as keyof Database)\n\t\t\t.set(updates)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.where(\"deleted_at\" as never, \"is\", null)\n\t\t\t.execute();\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Delete content (soft delete - moves to trash)\n\t */\n\tasync delete(type: string, id: string): Promise<boolean> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst result = await sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET deleted_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\treturn (result.numAffectedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Restore content from trash\n\t */\n\tasync restore(type: string, id: string): Promise<boolean> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET deleted_at = NULL\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NOT NULL\n\t\t`.execute(this.db);\n\n\t\treturn (result.numAffectedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Permanently delete content (cannot be undone)\n\t */\n\t/**\n\t * Permanently delete a soft-deleted content row.\n\t *\n\t * Returns `true` only when a soft-deleted (trashed) row was removed.\n\t * Returns `false` when no row exists OR when the row exists but is live —\n\t * the caller is responsible for distinguishing these cases (typically via\n\t * a follow-up `findByIdOrSlugIncludingTrashed` to surface NOT_FOUND vs\n\t * NOT_TRASHED). The `AND deleted_at IS NOT NULL` clause is the safety net\n\t * that prevents permanent delete from bypassing the trash workflow.\n\t */\n\tasync permanentDelete(type: string, id: string): Promise<boolean> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql`\n\t\t\tDELETE FROM ${sql.ref(tableName)}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NOT NULL\n\t\t`.execute(this.db);\n\n\t\treturn (result.numAffectedRows ?? 0n) > 0n;\n\t}\n\n\t/**\n\t * Find trashed content items\n\t */\n\tasync findTrashed(\n\t\ttype: string,\n\t\toptions: Omit<FindManyOptions, \"where\"> = {},\n\t): Promise<FindManyResult<ContentItem & { deletedAt: string }>> {\n\t\tconst tableName = getTableName(type);\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\t// Determine ordering - default to most recently deleted\n\t\tconst orderField = options.orderBy?.field || \"deletedAt\";\n\t\tconst orderDirection = options.orderBy?.direction || \"desc\";\n\t\tconst dbField = this.mapOrderField(orderField);\n\n\t\tconst safeOrderDirection = orderDirection.toLowerCase() === \"asc\" ? \"ASC\" : \"DESC\";\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.selectAll()\n\t\t\t.where(\"deleted_at\" as never, \"is not\", null);\n\n\t\t// Handle cursor pagination — decodeCursor throws on invalid input.\n\t\tif (options.cursor) {\n\t\t\tconst { orderValue, id: cursorId } = decodeCursor(options.cursor);\n\n\t\t\tif (safeOrderDirection === \"DESC\") {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \"<\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\teb.or([\n\t\t\t\t\t\teb(dbField as any, \">\", orderValue),\n\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \">\", cursorId)]),\n\t\t\t\t\t]),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tquery = query\n\t\t\t.orderBy(dbField as any, safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.orderBy(\"id\", safeOrderDirection === \"ASC\" ? \"asc\" : \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit);\n\n\t\tconst mappedResult: FindManyResult<ContentItem & { deletedAt: string }> = {\n\t\t\titems: items.map((row) => {\n\t\t\t\tconst record = row as Record<string, unknown>;\n\t\t\t\treturn {\n\t\t\t\t\t...this.mapRow(type, record),\n\t\t\t\t\tdeletedAt: typeof record.deleted_at === \"string\" ? record.deleted_at : \"\",\n\t\t\t\t};\n\t\t\t}),\n\t\t};\n\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst lastRow = items.at(-1) as Record<string, unknown>;\n\t\t\tconst lastOrderValue = lastRow[dbField];\n\t\t\tconst orderStr =\n\t\t\t\ttypeof lastOrderValue === \"string\" || typeof lastOrderValue === \"number\"\n\t\t\t\t\t? String(lastOrderValue)\n\t\t\t\t\t: \"\";\n\t\t\tmappedResult.nextCursor = encodeCursor(orderStr, String(lastRow.id));\n\t\t}\n\n\t\treturn mappedResult;\n\t}\n\n\t/**\n\t * Count trashed content items\n\t */\n\tasync countTrashed(type: string): Promise<number> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"deleted_at\" as never, \"is not\", null)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Count content items\n\t */\n\tasync count(\n\t\ttype: string,\n\t\twhere?: { status?: string; authorId?: string; locale?: string },\n\t): Promise<number> {\n\t\tconst tableName = getTableName(type);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"deleted_at\" as never, \"is\", null);\n\n\t\tif (where?.status) {\n\t\t\tquery = query.where(\"status\", \"=\", where.status);\n\t\t}\n\n\t\tif (where?.authorId) {\n\t\t\tquery = query.where(\"author_id\", \"=\", where.authorId);\n\t\t}\n\n\t\tif (where?.locale) {\n\t\t\tquery = query.where(\"locale\" as any, \"=\", where.locale);\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t// get overall statistics (total, published, draft) for a content type in a single query\n\tasync getStats(type: string): Promise<{ total: number; published: number; draft: number }> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await this.db\n\t\t\t.selectFrom(tableName as keyof Database)\n\t\t\t.select((eb) => [\n\t\t\t\teb.fn.count(\"id\").as(\"total\"),\n\t\t\t\teb.fn.sum(eb.case().when(\"status\", \"=\", \"published\").then(1).else(0).end()).as(\"published\"),\n\t\t\t\teb.fn.sum(eb.case().when(\"status\", \"=\", \"draft\").then(1).else(0).end()).as(\"draft\"),\n\t\t\t])\n\t\t\t.where(\"deleted_at\" as never, \"is\", null)\n\t\t\t.executeTakeFirst();\n\n\t\treturn {\n\t\t\ttotal: Number(result?.total || 0),\n\t\t\tpublished: Number(result?.published || 0),\n\t\t\tdraft: Number(result?.draft || 0),\n\t\t};\n\t}\n\n\t/**\n\t * Schedule content for future publishing\n\t *\n\t * Sets status to 'scheduled' and stores the scheduled publish time.\n\t * The content will be auto-published when the scheduled time is reached.\n\t */\n\tasync schedule(type: string, id: string, scheduledAt: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\t// Validate scheduledAt is in the future\n\t\tconst scheduledDate = new Date(scheduledAt);\n\t\tif (isNaN(scheduledDate.getTime())) {\n\t\t\tthrow new EmDashValidationError(\"Invalid scheduled date\");\n\t\t}\n\t\tif (scheduledDate <= new Date()) {\n\t\t\tthrow new EmDashValidationError(\"Scheduled date must be in the future\");\n\t\t}\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// Published posts keep their status — the schedule applies to the\n\t\t// pending draft, not the currently-live revision. Unpublished posts\n\t\t// transition to 'scheduled' so they aren't visible before the time.\n\t\tconst newStatus = existing.status === \"published\" ? \"published\" : \"scheduled\";\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET status = ${newStatus},\n\t\t\t\tscheduled_at = ${scheduledAt},\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Unschedule content\n\t *\n\t * Clears the scheduled time. Published posts stay published;\n\t * draft/scheduled posts revert to 'draft'.\n\t */\n\tasync unschedule(type: string, id: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// Published posts keep their status — just clear the pending schedule.\n\t\t// Draft/scheduled posts revert to 'draft'.\n\t\tconst newStatus = existing.status === \"published\" ? \"published\" : \"draft\";\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET status = ${newStatus},\n\t\t\t\tscheduled_at = NULL,\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND scheduled_at IS NOT NULL\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Find content that is ready to be published\n\t *\n\t * Returns all content where scheduled_at <= now, regardless of status.\n\t * This covers both draft-scheduled posts (status='scheduled') and\n\t * published posts with scheduled draft changes (status='published').\n\t */\n\tasync findReadyToPublish(type: string): Promise<ContentItem[]> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE scheduled_at IS NOT NULL\n\t\t\tAND scheduled_at <= ${now}\n\t\t\tAND deleted_at IS NULL\n\t\t\tORDER BY scheduled_at ASC\n\t\t`.execute(this.db);\n\n\t\treturn result.rows.map((row) => this.mapRow(type, row));\n\t}\n\n\t/**\n\t * Find all translations in a translation group\n\t */\n\tasync findTranslations(type: string, translationGroup: string): Promise<ContentItem[]> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<Record<string, unknown>>`\n\t\t\tSELECT * FROM ${sql.ref(tableName)}\n\t\t\tWHERE translation_group = ${translationGroup}\n\t\t\tAND deleted_at IS NULL\n\t\t\tORDER BY locale ASC\n\t\t`.execute(this.db);\n\n\t\treturn result.rows.map((row) => this.mapRow(type, row));\n\t}\n\n\t/**\n\t * Publish the current draft\n\t *\n\t * Promotes draft_revision_id to live_revision_id and clears draft pointer.\n\t * Syncs the draft revision's data into the content table columns so the\n\t * content table always reflects the published version.\n\t * If no draft revision exists, creates one from current data and publishes it.\n\t *\n\t * `publishedAt` (optional) overrides the publication timestamp. If omitted,\n\t * the existing `published_at` is preserved (idempotent re-publish keeps the\n\t * original date) and falls back to the current time on first publish. Pass\n\t * an explicit value to backdate a publish (e.g. when migrating content from\n\t * another CMS).\n\t */\n\tasync publish(type: string, id: string, publishedAt?: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\tlet revisionToPublish = existing.draftRevisionId || existing.liveRevisionId;\n\n\t\tif (!revisionToPublish) {\n\t\t\t// No revision exists - create one from current data\n\t\t\tconst revision = await revisionRepo.create({\n\t\t\t\tcollection: type,\n\t\t\t\tentryId: id,\n\t\t\t\tdata: existing.data,\n\t\t\t});\n\t\t\trevisionToPublish = revision.id;\n\t\t}\n\n\t\t// Sync the revision's data into the content table columns\n\t\t// so the content table always holds the published version\n\t\tconst revision = await revisionRepo.findById(revisionToPublish);\n\t\tif (revision) {\n\t\t\tawait this.syncDataColumns(type, id, revision.data);\n\n\t\t\t// Sync slug from revision if stored there\n\t\t\tif (typeof revision.data._slug === \"string\") {\n\t\t\t\tawait sql`\n\t\t\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\t\t\tSET slug = ${revision.data._slug}\n\t\t\t\t\tWHERE id = ${id}\n\t\t\t\t`.execute(this.db);\n\t\t\t}\n\t\t}\n\n\t\tif (publishedAt !== undefined) {\n\t\t\t// Caller supplied an explicit timestamp, so we overwrite published_at\n\t\t\t// directly (used to backdate a publish, e.g. for content migrations).\n\t\t\tawait sql`\n\t\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\t\tSET live_revision_id = ${revisionToPublish},\n\t\t\t\t\tdraft_revision_id = NULL,\n\t\t\t\t\tstatus = 'published',\n\t\t\t\t\tscheduled_at = NULL,\n\t\t\t\t\tpublished_at = ${publishedAt},\n\t\t\t\t\tupdated_at = ${now}\n\t\t\t\tWHERE id = ${id}\n\t\t\t\tAND deleted_at IS NULL\n\t\t\t`.execute(this.db);\n\t\t} else {\n\t\t\t// No timestamp supplied — preserve existing published_at on\n\t\t\t// idempotent re-publish, fall back to `now` on first publish.\n\t\t\tawait sql`\n\t\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\t\tSET live_revision_id = ${revisionToPublish},\n\t\t\t\t\tdraft_revision_id = NULL,\n\t\t\t\t\tstatus = 'published',\n\t\t\t\t\tscheduled_at = NULL,\n\t\t\t\t\tpublished_at = COALESCE(published_at, ${now}),\n\t\t\t\t\tupdated_at = ${now}\n\t\t\t\tWHERE id = ${id}\n\t\t\t\tAND deleted_at IS NULL\n\t\t\t`.execute(this.db);\n\t\t}\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Unpublish content\n\t *\n\t * Removes live pointer but preserves draft. If no draft exists,\n\t * creates one from the live version so the content isn't lost.\n\t */\n\tasync unpublish(type: string, id: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\t// If no draft exists, create one from the live version\n\t\tif (!existing.draftRevisionId && existing.liveRevisionId) {\n\t\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\t\tconst liveRevision = await revisionRepo.findById(existing.liveRevisionId);\n\t\t\tif (liveRevision) {\n\t\t\t\tconst draft = await revisionRepo.create({\n\t\t\t\t\tcollection: type,\n\t\t\t\t\tentryId: id,\n\t\t\t\t\tdata: liveRevision.data,\n\t\t\t\t});\n\n\t\t\t\tawait sql`\n\t\t\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\t\t\tSET draft_revision_id = ${draft.id}\n\t\t\t\t\tWHERE id = ${id}\n\t\t\t\t`.execute(this.db);\n\t\t\t}\n\t\t}\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET live_revision_id = NULL,\n\t\t\t\tstatus = 'draft',\n\t\t\t\tpublished_at = NULL,\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Set the draft revision pointer for a content item.\n\t *\n\t * Used by seed/import paths that stage a new revision's data before\n\t * promoting it to live via `publish()`.\n\t *\n\t * Validates that the content item exists and is not soft-deleted, that\n\t * the revision exists, and that the revision belongs to the same\n\t * collection and entry. Without these checks, a caller could leave the\n\t * content row pointing at a missing or unrelated revision.\n\t */\n\tasync setDraftRevision(type: string, id: string, revisionId: string): Promise<void> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\tconst revision = await revisionRepo.findById(revisionId);\n\t\tif (!revision) {\n\t\t\tthrow new EmDashValidationError(\"Revision not found\");\n\t\t}\n\n\t\tif (revision.collection !== type || revision.entryId !== id) {\n\t\t\tthrow new EmDashValidationError(\"Revision does not belong to the specified content item\");\n\t\t}\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET draft_revision_id = ${revisionId},\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\t}\n\n\t/**\n\t * Discard pending draft changes\n\t *\n\t * Clears draft_revision_id. The content table columns already hold the\n\t * published version, so no data sync is needed.\n\t */\n\tasync discardDraft(type: string, id: string): Promise<ContentItem> {\n\t\tconst tableName = getTableName(type);\n\t\tconst now = new Date().toISOString();\n\n\t\tconst existing = await this.findById(type, id);\n\t\tif (!existing) {\n\t\t\tthrow new EmDashValidationError(\"Content item not found\");\n\t\t}\n\n\t\tif (!existing.draftRevisionId) {\n\t\t\t// No draft to discard\n\t\t\treturn existing;\n\t\t}\n\n\t\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET draft_revision_id = NULL,\n\t\t\t\tupdated_at = ${now}\n\t\t\tWHERE id = ${id}\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst updated = await this.findById(type, id);\n\t\tif (!updated) {\n\t\t\tthrow new Error(\"Content not found\");\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Sync data columns in the content table from a data object.\n\t * Used to promote revision data into the content table on publish.\n\t * Keys starting with _ are revision metadata (e.g. _slug) and are skipped.\n\t */\n\tprivate async syncDataColumns(\n\t\ttype: string,\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t): Promise<void> {\n\t\tconst tableName = getTableName(type);\n\t\tconst updates: Record<string, unknown> = {};\n\n\t\tfor (const [key, value] of Object.entries(data)) {\n\t\t\tif (SYSTEM_COLUMNS.has(key)) continue;\n\t\t\tif (key.startsWith(\"_\")) continue; // revision metadata\n\t\t\tvalidateIdentifier(key, \"content field name\");\n\t\t\tupdates[key] = serializeValue(value);\n\t\t}\n\n\t\tif (Object.keys(updates).length === 0) return;\n\n\t\tawait this.db\n\t\t\t.updateTable(tableName as keyof Database)\n\t\t\t.set(updates)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Count content items with a pending schedule.\n\t * Includes both draft-scheduled (status='scheduled') and published\n\t * posts with scheduled draft changes (status='published', scheduled_at set).\n\t */\n\tasync countScheduled(type: string): Promise<number> {\n\t\tconst tableName = getTableName(type);\n\n\t\tconst result = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(id) as count FROM ${sql.ref(tableName)}\n\t\t\tWHERE scheduled_at IS NOT NULL\n\t\t\tAND deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\treturn Number(result.rows[0]?.count || 0);\n\t}\n\n\t/**\n\t * Map database row to ContentItem\n\t * Extracts system columns and puts content fields in data\n\t * Excludes null values from data to match input semantics\n\t */\n\tprivate mapRow(type: string, row: Record<string, unknown>): ContentItem {\n\t\tconst data: Record<string, unknown> = {};\n\n\t\tfor (const [key, value] of Object.entries(row)) {\n\t\t\tif (!SYSTEM_COLUMNS.has(key) && value !== null) {\n\t\t\t\tdata[key] = deserializeValue(value);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tid: row.id as string,\n\t\t\ttype,\n\t\t\tslug: row.slug as string | null,\n\t\t\tstatus: row.status as string,\n\t\t\tdata,\n\t\t\tauthorId: row.author_id as string | null,\n\t\t\tprimaryBylineId: (row.primary_byline_id as string | null) ?? null,\n\t\t\tcreatedAt: row.created_at as string,\n\t\t\tupdatedAt: row.updated_at as string,\n\t\t\tpublishedAt: row.published_at as string | null,\n\t\t\tscheduledAt: row.scheduled_at as string | null,\n\t\t\tliveRevisionId: (row.live_revision_id as string | null) ?? null,\n\t\t\tdraftRevisionId: (row.draft_revision_id as string | null) ?? null,\n\t\t\tversion: typeof row.version === \"number\" ? row.version : 1,\n\t\t\tlocale: (row.locale as string) ?? null,\n\t\t\ttranslationGroup: (row.translation_group as string) ?? null,\n\t\t};\n\t}\n\n\t/**\n\t * Map order field names to database columns.\n\t * Only allows known fields to prevent column enumeration via crafted orderBy values.\n\t */\n\tprivate mapOrderField(field: string): string {\n\t\tconst mapping: Record<string, string> = {\n\t\t\tcreatedAt: \"created_at\",\n\t\t\tupdatedAt: \"updated_at\",\n\t\t\tpublishedAt: \"published_at\",\n\t\t\tscheduledAt: \"scheduled_at\",\n\t\t\tdeletedAt: \"deleted_at\",\n\t\t\ttitle: \"title\",\n\t\t\tname: \"name\",\n\t\t\tslug: \"slug\",\n\t\t\tstatus: \"status\",\n\t\t\tlocale: \"locale\",\n\t\t};\n\n\t\tconst mapped = mapping[field];\n\t\tif (!mapped) {\n\t\t\tthrow new EmDashValidationError(`Invalid order field: ${field}`);\n\t\t}\n\t\treturn mapped;\n\t}\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import "../types-CS8FIX7L.mjs";
2
- import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-OURCaApa.mjs";
3
- import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-BKSf3T9R.mjs";
1
+ import "../types-Dtx1mSMX.mjs";
2
+ import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-Clwe4Mme.mjs";
3
+ import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-BktHA7EO.mjs";
4
4
  export { type DatabaseDescriptor, type DatabaseDialectType, type LibsqlConfig, type MigrationStatus, type PostgresConfig, type SqliteConfig, getMigrationStatus, libsql, postgres, rollbackMigration, runMigrations, sqlite };
package/dist/db/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import "../dialect-helpers-DhTzaUxP.mjs";
2
- import { n as rollbackMigration, r as runMigrations, t as getMigrationStatus } from "../runner-tQ7BJ4T7.mjs";
1
+ import { n as rollbackMigration, r as runMigrations, t as getMigrationStatus } from "../runner-DMnlIkh4.mjs";
2
+ import "../dialect-helpers-BKCvISIQ.mjs";
3
3
 
4
4
  //#region src/db/adapters.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { r as LibsqlConfig } from "../adapters-BKSf3T9R.mjs";
1
+ import { r as LibsqlConfig } from "../adapters-BktHA7EO.mjs";
2
2
  import { Dialect } from "kysely";
3
3
 
4
4
  //#region src/db/libsql.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"libsql.d.mts","names":[],"sources":["../../src/db/libsql.ts"],"mappings":";;;;;;;iBAcgB,aAAA,CAAc,MAAA,EAAQ,YAAA,GAAe,OAAA"}
1
+ {"version":3,"file":"libsql.d.mts","names":[],"sources":["../../src/db/libsql.ts"],"mappings":";;;;;;;iBAegB,aAAA,CAAc,MAAA,EAAQ,YAAA,GAAe,OAAA"}
@@ -1,11 +1,16 @@
1
- import { n as __require } from "../chunk-ClPoSABd.mjs";
1
+ import { LibsqlDialect } from "@libsql/kysely-libsql";
2
2
 
3
3
  //#region src/db/libsql.ts
4
4
  /**
5
+ * libSQL runtime adapter
6
+ *
7
+ * Creates a Kysely dialect for libSQL/Turso.
8
+ * Loaded at runtime via virtual module.
9
+ */
10
+ /**
5
11
  * Create a libSQL dialect from config
6
12
  */
7
13
  function createDialect(config) {
8
- const { LibsqlDialect } = __require("@libsql/kysely-libsql");
9
14
  return new LibsqlDialect({
10
15
  url: config.url,
11
16
  authToken: config.authToken
@@ -1 +1 @@
1
- {"version":3,"file":"libsql.mjs","names":[],"sources":["../../src/db/libsql.ts"],"sourcesContent":["/**\n * libSQL runtime adapter\n *\n * Creates a Kysely dialect for libSQL/Turso.\n * Loaded at runtime via virtual module.\n */\n\nimport type { Dialect } from \"kysely\";\n\nimport type { LibsqlConfig } from \"./adapters.js\";\n\n/**\n * Create a libSQL dialect from config\n */\nexport function createDialect(config: LibsqlConfig): Dialect {\n\t// Dynamic import to avoid loading @libsql/kysely-libsql at config time\n\tconst { LibsqlDialect } = require(\"@libsql/kysely-libsql\");\n\n\treturn new LibsqlDialect({\n\t\turl: config.url,\n\t\tauthToken: config.authToken,\n\t});\n}\n"],"mappings":";;;;;;AAcA,SAAgB,cAAc,QAA+B;CAE5D,MAAM,EAAE,4BAA0B,wBAAwB;AAE1D,QAAO,IAAI,cAAc;EACxB,KAAK,OAAO;EACZ,WAAW,OAAO;EAClB,CAAC"}
1
+ {"version":3,"file":"libsql.mjs","names":[],"sources":["../../src/db/libsql.ts"],"sourcesContent":["/**\n * libSQL runtime adapter\n *\n * Creates a Kysely dialect for libSQL/Turso.\n * Loaded at runtime via virtual module.\n */\n\nimport { LibsqlDialect } from \"@libsql/kysely-libsql\";\nimport type { Dialect } from \"kysely\";\n\nimport type { LibsqlConfig } from \"./adapters.js\";\n\n/**\n * Create a libSQL dialect from config\n */\nexport function createDialect(config: LibsqlConfig): Dialect {\n\treturn new LibsqlDialect({\n\t\turl: config.url,\n\t\tauthToken: config.authToken,\n\t});\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAgB,cAAc,QAA+B;AAC5D,QAAO,IAAI,cAAc;EACxB,KAAK,OAAO;EACZ,WAAW,OAAO;EAClB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { i as PostgresConfig } from "../adapters-BKSf3T9R.mjs";
1
+ import { i as PostgresConfig } from "../adapters-BktHA7EO.mjs";
2
2
  import { PostgresDialect } from "kysely";
3
3
 
4
4
  //#region src/db/postgres.d.ts
@@ -1,4 +1,4 @@
1
- import { a as SqliteConfig } from "../adapters-BKSf3T9R.mjs";
1
+ import { a as SqliteConfig } from "../adapters-BktHA7EO.mjs";
2
2
  import { Dialect } from "kysely";
3
3
 
4
4
  //#region src/db/sqlite.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.mts","names":[],"sources":["../../src/db/sqlite.ts"],"mappings":";;;;;;;iBAcgB,aAAA,CAAc,MAAA,EAAQ,YAAA,GAAe,OAAA"}
1
+ {"version":3,"file":"sqlite.d.mts","names":[],"sources":["../../src/db/sqlite.ts"],"mappings":";;;;;;;iBAegB,aAAA,CAAc,MAAA,EAAQ,YAAA,GAAe,OAAA"}
@@ -1,12 +1,17 @@
1
- import { n as __require } from "../chunk-ClPoSABd.mjs";
1
+ import BetterSqlite3 from "better-sqlite3";
2
+ import { SqliteDialect } from "kysely";
2
3
 
3
4
  //#region src/db/sqlite.ts
4
5
  /**
6
+ * SQLite runtime adapter
7
+ *
8
+ * Creates a Kysely dialect for better-sqlite3.
9
+ * Loaded at runtime via virtual module.
10
+ */
11
+ /**
5
12
  * Create a SQLite dialect from config
6
13
  */
7
14
  function createDialect(config) {
8
- const BetterSqlite3 = __require("better-sqlite3");
9
- const { SqliteDialect } = __require("kysely");
10
15
  const url = config.url;
11
16
  return new SqliteDialect({ database: new BetterSqlite3(url.startsWith("file:") ? url.slice(5) : url) });
12
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.mjs","names":[],"sources":["../../src/db/sqlite.ts"],"sourcesContent":["/**\n * SQLite runtime adapter\n *\n * Creates a Kysely dialect for better-sqlite3.\n * Loaded at runtime via virtual module.\n */\n\nimport type { Dialect } from \"kysely\";\n\nimport type { SqliteConfig } from \"./adapters.js\";\n\n/**\n * Create a SQLite dialect from config\n */\nexport function createDialect(config: SqliteConfig): Dialect {\n\t// Dynamic import to avoid loading better-sqlite3 at config time\n\tconst BetterSqlite3 = require(\"better-sqlite3\");\n\tconst { SqliteDialect } = require(\"kysely\");\n\n\t// Parse URL to get file path\n\tconst url = config.url;\n\tconst filePath = url.startsWith(\"file:\") ? url.slice(5) : url;\n\n\tconst database = new BetterSqlite3(filePath);\n\n\treturn new SqliteDialect({ database });\n}\n"],"mappings":";;;;;;AAcA,SAAgB,cAAc,QAA+B;CAE5D,MAAM,0BAAwB,iBAAiB;CAC/C,MAAM,EAAE,4BAA0B,SAAS;CAG3C,MAAM,MAAM,OAAO;AAKnB,QAAO,IAAI,cAAc,EAAE,UAFV,IAAI,cAFJ,IAAI,WAAW,QAAQ,GAAG,IAAI,MAAM,EAAE,GAAG,IAEd,EAEP,CAAC"}
1
+ {"version":3,"file":"sqlite.mjs","names":[],"sources":["../../src/db/sqlite.ts"],"sourcesContent":["/**\n * SQLite runtime adapter\n *\n * Creates a Kysely dialect for better-sqlite3.\n * Loaded at runtime via virtual module.\n */\n\nimport BetterSqlite3 from \"better-sqlite3\";\nimport { type Dialect, SqliteDialect } from \"kysely\";\n\nimport type { SqliteConfig } from \"./adapters.js\";\n\n/**\n * Create a SQLite dialect from config\n */\nexport function createDialect(config: SqliteConfig): Dialect {\n\t// Parse URL to get file path\n\tconst url = config.url;\n\tconst filePath = url.startsWith(\"file:\") ? url.slice(5) : url;\n\n\tconst database = new BetterSqlite3(filePath);\n\n\treturn new SqliteDialect({ database });\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,SAAgB,cAAc,QAA+B;CAE5D,MAAM,MAAM,OAAO;AAKnB,QAAO,IAAI,cAAc,EAAE,UAFV,IAAI,cAFJ,IAAI,WAAW,QAAQ,GAAG,IAAI,MAAM,EAAE,GAAG,IAEd,EAEP,CAAC"}
@@ -38,4 +38,4 @@ function isMissingTableError(error) {
38
38
 
39
39
  //#endregion
40
40
  export { isMissingTableError as t };
41
- //# sourceMappingURL=db-errors-l1Qh2RPR.mjs.map
41
+ //# sourceMappingURL=db-errors-B7P2pSCn.mjs.map