emdash 0.1.1 → 0.2.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 (192) hide show
  1. package/dist/{adapters-BLMa4JGD.d.mts → adapters-N6BF7RCD.d.mts} +1 -1
  2. package/dist/{adapters-BLMa4JGD.d.mts.map → adapters-N6BF7RCD.d.mts.map} +1 -1
  3. package/dist/{apply-kC39ev1Z.mjs → apply-wmVEOSbR.mjs} +56 -9
  4. package/dist/apply-wmVEOSbR.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.mjs +80 -27
  7. package/dist/astro/index.mjs.map +1 -1
  8. package/dist/astro/middleware/auth.d.mts +5 -5
  9. package/dist/astro/middleware/auth.d.mts.map +1 -1
  10. package/dist/astro/middleware/auth.mjs +127 -56
  11. package/dist/astro/middleware/auth.mjs.map +1 -1
  12. package/dist/astro/middleware/request-context.mjs +1 -1
  13. package/dist/astro/middleware/setup.mjs +1 -1
  14. package/dist/astro/middleware.d.mts.map +1 -1
  15. package/dist/astro/middleware.mjs +74 -39
  16. package/dist/astro/middleware.mjs.map +1 -1
  17. package/dist/astro/types.d.mts +30 -9
  18. package/dist/astro/types.d.mts.map +1 -1
  19. package/dist/{byline-CL847F26.mjs → byline-1WQPlISL.mjs} +51 -29
  20. package/dist/byline-1WQPlISL.mjs.map +1 -0
  21. package/dist/{bylines-C2a-2TGt.mjs → bylines-BYdTYmia.mjs} +10 -8
  22. package/dist/{bylines-C2a-2TGt.mjs.map → bylines-BYdTYmia.mjs.map} +1 -1
  23. package/dist/cli/index.mjs +15 -12
  24. package/dist/cli/index.mjs.map +1 -1
  25. package/dist/client/cf-access.d.mts +1 -1
  26. package/dist/client/index.d.mts +1 -1
  27. package/dist/client/index.mjs +1 -1
  28. package/dist/{config-CKE8p9xM.mjs → config-Cq8H0SfX.mjs} +2 -10
  29. package/dist/{config-CKE8p9xM.mjs.map → config-Cq8H0SfX.mjs.map} +1 -1
  30. package/dist/{content-D6C2WsZC.mjs → content-BmXndhdi.mjs} +16 -3
  31. package/dist/content-BmXndhdi.mjs.map +1 -0
  32. package/dist/db/index.d.mts +3 -3
  33. package/dist/db/index.mjs +1 -1
  34. package/dist/db/libsql.d.mts +1 -1
  35. package/dist/db/postgres.d.mts +1 -1
  36. package/dist/db/sqlite.d.mts +1 -1
  37. package/dist/{default-Cyi4aAxu.mjs → default-WYlzADZL.mjs} +1 -1
  38. package/dist/{default-Cyi4aAxu.mjs.map → default-WYlzADZL.mjs.map} +1 -1
  39. package/dist/{error-Cxz0tQeO.mjs → error-DrxtnGPg.mjs} +1 -1
  40. package/dist/{error-Cxz0tQeO.mjs.map → error-DrxtnGPg.mjs.map} +1 -1
  41. package/dist/{index-CLBc4gw-.d.mts → index-UHEVQMus.d.mts} +55 -17
  42. package/dist/index-UHEVQMus.d.mts.map +1 -0
  43. package/dist/index.d.mts +11 -11
  44. package/dist/index.mjs +17 -17
  45. package/dist/{load-yOOlckBj.mjs → load-Veizk2cT.mjs} +1 -1
  46. package/dist/{load-yOOlckBj.mjs.map → load-Veizk2cT.mjs.map} +1 -1
  47. package/dist/{loader-fz8Q_3EO.mjs → loader-CHb2v0jm.mjs} +1 -1
  48. package/dist/{loader-fz8Q_3EO.mjs.map → loader-CHb2v0jm.mjs.map} +1 -1
  49. package/dist/{manifest-schema-CL8DWO9b.mjs → manifest-schema-CuMio1A9.mjs} +1 -1
  50. package/dist/{manifest-schema-CL8DWO9b.mjs.map → manifest-schema-CuMio1A9.mjs.map} +1 -1
  51. package/dist/media/index.d.mts +1 -1
  52. package/dist/media/local-runtime.d.mts +7 -7
  53. package/dist/{mode-C2EzN1uE.mjs → mode-CYeM2rPt.mjs} +1 -1
  54. package/dist/{mode-C2EzN1uE.mjs.map → mode-CYeM2rPt.mjs.map} +1 -1
  55. package/dist/page/index.d.mts +10 -1
  56. package/dist/page/index.d.mts.map +1 -1
  57. package/dist/page/index.mjs +8 -4
  58. package/dist/page/index.mjs.map +1 -1
  59. package/dist/{placeholder-SvFCKbz_.d.mts → placeholder-bOx1xCTY.d.mts} +1 -1
  60. package/dist/{placeholder-SvFCKbz_.d.mts.map → placeholder-bOx1xCTY.d.mts.map} +1 -1
  61. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  62. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  63. package/dist/{query-BVYN0PJ6.mjs → query-5Hcv_5ER.mjs} +20 -8
  64. package/dist/{query-BVYN0PJ6.mjs.map → query-5Hcv_5ER.mjs.map} +1 -1
  65. package/dist/{registry-BNYQKX_d.mjs → registry-1EvbAfsC.mjs} +6 -2
  66. package/dist/{registry-BNYQKX_d.mjs.map → registry-1EvbAfsC.mjs.map} +1 -1
  67. package/dist/{runner-BraqvGYk.mjs → runner-BoN0-FPi.mjs} +155 -130
  68. package/dist/runner-BoN0-FPi.mjs.map +1 -0
  69. package/dist/{runner-EAtf0ZIe.d.mts → runner-DTqkzOzc.d.mts} +2 -2
  70. package/dist/{runner-EAtf0ZIe.d.mts.map → runner-DTqkzOzc.d.mts.map} +1 -1
  71. package/dist/runtime.d.mts +6 -6
  72. package/dist/runtime.mjs +1 -1
  73. package/dist/{search-C1gg67nN.mjs → search-BsYMed12.mjs} +235 -105
  74. package/dist/search-BsYMed12.mjs.map +1 -0
  75. package/dist/seed/index.d.mts +2 -2
  76. package/dist/seed/index.mjs +8 -8
  77. package/dist/seo/index.d.mts +1 -1
  78. package/dist/storage/local.d.mts +1 -1
  79. package/dist/storage/local.mjs +1 -1
  80. package/dist/storage/s3.d.mts +1 -1
  81. package/dist/storage/s3.mjs +1 -1
  82. package/dist/{tokens-DpgrkrXK.mjs → tokens-DrB-W6Q-.mjs} +1 -1
  83. package/dist/{tokens-DpgrkrXK.mjs.map → tokens-DrB-W6Q-.mjs.map} +1 -1
  84. package/dist/{transport-yxiQsi8I.mjs → transport-Bl8cTdYt.mjs} +1 -1
  85. package/dist/{transport-yxiQsi8I.mjs.map → transport-Bl8cTdYt.mjs.map} +1 -1
  86. package/dist/{transport-BFGblqwG.d.mts → transport-COOs9GSE.d.mts} +1 -1
  87. package/dist/{transport-BFGblqwG.d.mts.map → transport-COOs9GSE.d.mts.map} +1 -1
  88. package/dist/{types-BQo5JS0J.d.mts → types-6dqxBqsH.d.mts} +80 -106
  89. package/dist/types-6dqxBqsH.d.mts.map +1 -0
  90. package/dist/{types-DRjfYOEv.d.mts → types-7-UjSEyB.d.mts} +1 -1
  91. package/dist/{types-DRjfYOEv.d.mts.map → types-7-UjSEyB.d.mts.map} +1 -1
  92. package/dist/{types-CUBbjgmP.mjs → types-Bec-r_3_.mjs} +1 -1
  93. package/dist/{types-CUBbjgmP.mjs.map → types-Bec-r_3_.mjs.map} +1 -1
  94. package/dist/{types-DaNLHo_T.d.mts → types-BljtYPSd.d.mts} +1 -1
  95. package/dist/{types-DaNLHo_T.d.mts.map → types-BljtYPSd.d.mts.map} +1 -1
  96. package/dist/{types-BRuPJGdV.d.mts → types-CIsTnQvJ.d.mts} +3 -1
  97. package/dist/types-CIsTnQvJ.d.mts.map +1 -0
  98. package/dist/types-CMMN0pNg.mjs.map +1 -1
  99. package/dist/{types-DPfzHnjW.d.mts → types-CcreFIIH.d.mts} +1 -1
  100. package/dist/{types-DPfzHnjW.d.mts.map → types-CcreFIIH.d.mts.map} +1 -1
  101. package/dist/{types-CiA5Gac0.mjs → types-DuNbGKjF.mjs} +1 -1
  102. package/dist/{types-CiA5Gac0.mjs.map → types-DuNbGKjF.mjs.map} +1 -1
  103. package/dist/{validate-HtxZeaBi.d.mts → validate-B7KP7VLM.d.mts} +4 -4
  104. package/dist/{validate-HtxZeaBi.d.mts.map → validate-B7KP7VLM.d.mts.map} +1 -1
  105. package/dist/{validate-_rsF-Dx_.mjs → validate-CXnRKfJK.mjs} +2 -2
  106. package/dist/{validate-_rsF-Dx_.mjs.map → validate-CXnRKfJK.mjs.map} +1 -1
  107. package/package.json +6 -6
  108. package/src/api/csrf.ts +13 -2
  109. package/src/api/handlers/content.ts +7 -0
  110. package/src/api/handlers/dashboard.ts +4 -8
  111. package/src/api/handlers/device-flow.ts +55 -37
  112. package/src/api/handlers/index.ts +6 -1
  113. package/src/api/handlers/seo.ts +48 -21
  114. package/src/api/public-url.ts +84 -0
  115. package/src/api/schemas/content.ts +2 -2
  116. package/src/api/schemas/menus.ts +12 -2
  117. package/src/astro/integration/index.ts +30 -7
  118. package/src/astro/integration/routes.ts +13 -2
  119. package/src/astro/integration/runtime.ts +7 -5
  120. package/src/astro/integration/vite-config.ts +52 -9
  121. package/src/astro/middleware/auth.ts +60 -56
  122. package/src/astro/middleware/csp.ts +25 -0
  123. package/src/astro/middleware.ts +31 -3
  124. package/src/astro/routes/PluginRegistry.tsx +8 -2
  125. package/src/astro/routes/admin.astro +7 -2
  126. package/src/astro/routes/api/admin/users/[id]/disable.ts +18 -12
  127. package/src/astro/routes/api/admin/users/[id]/index.ts +26 -5
  128. package/src/astro/routes/api/auth/invite/complete.ts +3 -2
  129. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +2 -1
  130. package/src/astro/routes/api/auth/oauth/[provider].ts +2 -1
  131. package/src/astro/routes/api/auth/passkey/options.ts +3 -2
  132. package/src/astro/routes/api/auth/passkey/register/options.ts +3 -2
  133. package/src/astro/routes/api/auth/passkey/register/verify.ts +3 -2
  134. package/src/astro/routes/api/auth/passkey/verify.ts +3 -2
  135. package/src/astro/routes/api/auth/signup/complete.ts +3 -2
  136. package/src/astro/routes/api/content/[collection]/index.ts +31 -3
  137. package/src/astro/routes/api/import/wordpress/execute.ts +9 -0
  138. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +10 -0
  139. package/src/astro/routes/api/manifest.ts +1 -0
  140. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
  141. package/src/astro/routes/api/oauth/authorize.ts +12 -7
  142. package/src/astro/routes/api/oauth/device/code.ts +5 -1
  143. package/src/astro/routes/api/setup/admin-verify.ts +3 -2
  144. package/src/astro/routes/api/setup/admin.ts +3 -2
  145. package/src/astro/routes/api/setup/dev-bypass.ts +2 -1
  146. package/src/astro/routes/api/setup/index.ts +3 -2
  147. package/src/astro/routes/api/snapshot.ts +2 -1
  148. package/src/astro/routes/api/themes/preview.ts +2 -1
  149. package/src/astro/routes/api/well-known/auth.ts +1 -0
  150. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +3 -2
  151. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
  152. package/src/astro/routes/robots.txt.ts +5 -1
  153. package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
  154. package/src/astro/routes/sitemap.xml.ts +18 -23
  155. package/src/astro/types.ts +27 -1
  156. package/src/auth/passkey-config.ts +6 -10
  157. package/src/bylines/index.ts +11 -8
  158. package/src/cli/commands/login.ts +5 -2
  159. package/src/components/InlinePortableTextEditor.tsx +5 -3
  160. package/src/content/converters/portable-text-to-prosemirror.ts +50 -2
  161. package/src/database/migrations/034_published_at_index.ts +29 -0
  162. package/src/database/migrations/runner.ts +2 -0
  163. package/src/database/repositories/byline.ts +48 -42
  164. package/src/database/repositories/content.ts +23 -1
  165. package/src/database/repositories/options.ts +9 -3
  166. package/src/database/repositories/seo.ts +34 -17
  167. package/src/database/repositories/types.ts +2 -0
  168. package/src/emdash-runtime.ts +61 -18
  169. package/src/import/index.ts +1 -1
  170. package/src/import/sources/wxr.ts +45 -2
  171. package/src/index.ts +9 -1
  172. package/src/mcp/server.ts +85 -5
  173. package/src/menus/index.ts +2 -1
  174. package/src/page/context.ts +13 -1
  175. package/src/page/jsonld.ts +10 -6
  176. package/src/page/seo-contributions.ts +1 -1
  177. package/src/plugins/context.ts +145 -35
  178. package/src/plugins/manager.ts +12 -0
  179. package/src/plugins/types.ts +80 -4
  180. package/src/query.ts +18 -0
  181. package/src/schema/registry.ts +5 -0
  182. package/src/settings/index.ts +64 -0
  183. package/src/utils/chunks.ts +17 -0
  184. package/dist/apply-kC39ev1Z.mjs.map +0 -1
  185. package/dist/byline-CL847F26.mjs.map +0 -1
  186. package/dist/content-D6C2WsZC.mjs.map +0 -1
  187. package/dist/index-CLBc4gw-.d.mts.map +0 -1
  188. package/dist/runner-BraqvGYk.mjs.map +0 -1
  189. package/dist/search-C1gg67nN.mjs.map +0 -1
  190. package/dist/types-BQo5JS0J.d.mts.map +0 -1
  191. package/dist/types-BRuPJGdV.d.mts.map +0 -1
  192. /package/src/astro/routes/api/media/file/{[key].ts → [...key].ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { t as Interceptor } from "../transport-BFGblqwG.mjs";
1
+ import { t as Interceptor } from "../transport-COOs9GSE.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-BFGblqwG.mjs";
1
+ import { a as tokenInterceptor, i as devBypassInterceptor, n as createTransport, r as csrfInterceptor, t as Interceptor } from "../transport-COOs9GSE.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-yxiQsi8I.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-Bl8cTdYt.mjs";
2
2
  import mime from "mime/lite";
3
3
 
4
4
  //#region src/client/index.ts
@@ -1,12 +1,4 @@
1
- import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
2
-
3
1
  //#region src/i18n/config.ts
4
- var config_exports = /* @__PURE__ */ __exportAll({
5
- getFallbackChain: () => getFallbackChain,
6
- getI18nConfig: () => getI18nConfig,
7
- isI18nEnabled: () => isI18nEnabled,
8
- setI18nConfig: () => setI18nConfig
9
- });
10
2
  let _config;
11
3
  /**
12
4
  * Initialize i18n config from virtual module data.
@@ -51,5 +43,5 @@ function getFallbackChain(locale) {
51
43
  }
52
44
 
53
45
  //#endregion
54
- export { setI18nConfig as a, isI18nEnabled as i, getFallbackChain as n, getI18nConfig as r, config_exports as t };
55
- //# sourceMappingURL=config-CKE8p9xM.mjs.map
46
+ export { setI18nConfig as i, getI18nConfig as n, isI18nEnabled as r, getFallbackChain as t };
47
+ //# sourceMappingURL=config-Cq8H0SfX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-CKE8p9xM.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-Cq8H0SfX.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"}
@@ -199,7 +199,7 @@ var ContentRepository = class {
199
199
  async create(input) {
200
200
  const id = ulid();
201
201
  const now = (/* @__PURE__ */ new Date()).toISOString();
202
- const { type, slug, data, status = "draft", authorId, primaryBylineId, locale, translationOf, publishedAt } = input;
202
+ const { type, slug, data, status = "draft", authorId, primaryBylineId, locale, translationOf, publishedAt, createdAt } = input;
203
203
  if (!type) throw new EmDashValidationError("Content type is required");
204
204
  const tableName = getTableName(type);
205
205
  let translationGroup = id;
@@ -227,7 +227,7 @@ var ContentRepository = class {
227
227
  status,
228
228
  authorId || null,
229
229
  primaryBylineId ?? null,
230
- now,
230
+ createdAt || now,
231
231
  now,
232
232
  publishedAt || null,
233
233
  1,
@@ -555,6 +555,19 @@ var ContentRepository = class {
555
555
  const result = await query.executeTakeFirst();
556
556
  return Number(result?.count || 0);
557
557
  }
558
+ async getStats(type) {
559
+ const tableName = getTableName(type);
560
+ const result = await this.db.selectFrom(tableName).select((eb) => [
561
+ eb.fn.count("id").as("total"),
562
+ eb.fn.sum(eb.case().when("status", "=", "published").then(1).else(0).end()).as("published"),
563
+ eb.fn.sum(eb.case().when("status", "=", "draft").then(1).else(0).end()).as("draft")
564
+ ]).where("deleted_at", "is", null).executeTakeFirst();
565
+ return {
566
+ total: Number(result?.total || 0),
567
+ published: Number(result?.published || 0),
568
+ draft: Number(result?.draft || 0)
569
+ };
570
+ }
558
571
  /**
559
572
  * Schedule content for future publishing
560
573
  *
@@ -821,4 +834,4 @@ var ContentRepository = class {
821
834
 
822
835
  //#endregion
823
836
  export { slugify as i, content_exports as n, RevisionRepository as r, ContentRepository as t };
824
- //# sourceMappingURL=content-D6C2WsZC.mjs.map
837
+ //# sourceMappingURL=content-BmXndhdi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-BmXndhdi.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 */\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 { 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\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\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\n\t\tif (options.cursor) {\n\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\tif (decoded) {\n\t\t\t\tconst { orderValue, id: cursorId } = decoded;\n\n\t\t\t\tif (safeOrderDirection === \"DESC\") {\n\t\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\t\teb.or([\n\t\t\t\t\t\t\teb(dbField as any, \"<\", orderValue),\n\t\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\t\teb.or([\n\t\t\t\t\t\t\teb(dbField as any, \">\", orderValue),\n\t\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \">\", cursorId)]),\n\t\t\t\t\t\t]),\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\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\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`.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\n\t\tif (options.cursor) {\n\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\tif (decoded) {\n\t\t\t\tconst { orderValue, id: cursorId } = decoded;\n\n\t\t\t\tif (safeOrderDirection === \"DESC\") {\n\t\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\t\teb.or([\n\t\t\t\t\t\t\teb(dbField as any, \"<\", orderValue),\n\t\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \"<\", cursorId)]),\n\t\t\t\t\t\t]),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tquery = query.where((eb) =>\n\t\t\t\t\t\teb.or([\n\t\t\t\t\t\t\teb(dbField as any, \">\", orderValue),\n\t\t\t\t\t\t\teb.and([eb(dbField as any, \"=\", orderValue), eb(\"id\", \">\", cursorId)]),\n\t\t\t\t\t\t]),\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\tasync publish(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\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\tawait sql`\n\t\t\tUPDATE ${sql.ref(tableName)}\n\t\t\tSET live_revision_id = ${revisionToPublish},\n\t\t\t\tdraft_revision_id = NULL,\n\t\t\t\tstatus = 'published',\n\t\t\t\tscheduled_at = NULL,\n\t\t\t\tpublished_at = COALESCE(published_at, ${now}),\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 * 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\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 * 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\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\tslug: \"slug\",\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-DRjfYOEv.mjs";
2
- import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-EAtf0ZIe.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-BLMa4JGD.mjs";
1
+ import "../types-7-UjSEyB.mjs";
2
+ import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-DTqkzOzc.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-N6BF7RCD.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
1
  import "../dialect-helpers-B9uSp2GJ.mjs";
2
- import { n as rollbackMigration, r as runMigrations, t as getMigrationStatus } from "../runner-BraqvGYk.mjs";
2
+ import { n as rollbackMigration, r as runMigrations, t as getMigrationStatus } from "../runner-BoN0-FPi.mjs";
3
3
 
4
4
  //#region src/db/adapters.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { r as LibsqlConfig } from "../adapters-BLMa4JGD.mjs";
1
+ import { r as LibsqlConfig } from "../adapters-N6BF7RCD.mjs";
2
2
  import { Dialect } from "kysely";
3
3
 
4
4
  //#region src/db/libsql.d.ts
@@ -1,4 +1,4 @@
1
- import { i as PostgresConfig } from "../adapters-BLMa4JGD.mjs";
1
+ import { i as PostgresConfig } from "../adapters-N6BF7RCD.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-BLMa4JGD.mjs";
1
+ import { a as SqliteConfig } from "../adapters-N6BF7RCD.mjs";
2
2
  import { Dialect } from "kysely";
3
3
 
4
4
  //#region src/db/sqlite.d.ts
@@ -78,4 +78,4 @@ const defaultSeed = {
78
78
 
79
79
  //#endregion
80
80
  export { defaultSeed as t };
81
- //# sourceMappingURL=default-Cyi4aAxu.mjs.map
81
+ //# sourceMappingURL=default-WYlzADZL.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-Cyi4aAxu.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
1
+ {"version":3,"file":"default-WYlzADZL.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
@@ -24,4 +24,4 @@ function apiError(code, message, status) {
24
24
 
25
25
  //#endregion
26
26
  export { apiError as t };
27
- //# sourceMappingURL=error-Cxz0tQeO.mjs.map
27
+ //# sourceMappingURL=error-DrxtnGPg.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-Cxz0tQeO.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ error: { code, message } }` with correct Content-Type.\n * Use this for all error responses in API routes.\n */\nexport function apiError(code: string, message: string, status: number): Response {\n\treturn Response.json({ error: { code, message } }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(result.error.code, result.error.message, mapErrorStatus(result.error.code));\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;AAmBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SAAS,MAAc,SAAiB,QAA0B;AACjF,QAAO,SAAS,KAAK,EAAE,OAAO;EAAE;EAAM;EAAS,EAAE,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC"}
1
+ {"version":3,"file":"error-DrxtnGPg.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ error: { code, message } }` with correct Content-Type.\n * Use this for all error responses in API routes.\n */\nexport function apiError(code: string, message: string, status: number): Response {\n\treturn Response.json({ error: { code, message } }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(result.error.code, result.error.message, mapErrorStatus(result.error.code));\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;AAmBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SAAS,MAAc,SAAiB,QAA0B;AACjF,QAAO,SAAS,KAAK,EAAE,OAAO;EAAE;EAAM;EAAS,EAAE,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC"}
@@ -1,11 +1,11 @@
1
- import { _ as MediaValue, m as MediaProviderDescriptor } from "./placeholder-SvFCKbz_.mjs";
2
- import { t as Database } from "./types-DRjfYOEv.mjs";
3
- import { a as ContentSeoInput, c as FindManyOptions, l as FindManyResult, o as CreateContentInput, r as ContentItem, t as BylineSummary, u as UpdateContentInput } from "./types-BRuPJGdV.mjs";
4
- import { D as PageMetadataContribution, G as RequestMeta, L as PluginDefinition, O as PageMetadataEvent, Q as StandardPluginDefinition, T as PageFragmentEvent, V as PluginStorageConfig, f as CronEvent, i as CommentAfterModerateEvent, n as CommentAfterCreateEvent, o as CommentBeforeCreateEvent, p as EmailMessage, q as ResolvedPlugin, w as PageFragmentContribution, x as MediaItem$1, z as PluginManifest } from "./types-BQo5JS0J.mjs";
5
- import { g as UpdateFieldInput, h as UpdateCollectionInput, i as CollectionWithFields, l as Field, o as CreateCollectionInput, s as CreateFieldInput, t as Collection } from "./types-DPfzHnjW.mjs";
6
- import { C as SiteSettingKey, w as SiteSettings } from "./validate-HtxZeaBi.mjs";
7
- import { d as Storage } from "./types-DaNLHo_T.mjs";
8
- import { t as DatabaseDescriptor } from "./adapters-BLMa4JGD.mjs";
1
+ import { _ as MediaValue, m as MediaProviderDescriptor } from "./placeholder-bOx1xCTY.mjs";
2
+ import { t as Database } from "./types-7-UjSEyB.mjs";
3
+ import { a as ContentSeoInput, c as FindManyOptions, l as FindManyResult, o as CreateContentInput, r as ContentItem, t as BylineSummary, u as UpdateContentInput } from "./types-CIsTnQvJ.mjs";
4
+ import { g as UpdateFieldInput, h as UpdateCollectionInput, i as CollectionWithFields, l as Field, o as CreateCollectionInput, s as CreateFieldInput, t as Collection } from "./types-CcreFIIH.mjs";
5
+ import { $ as StandardPluginDefinition, B as PluginManifest, E as PageFragmentEvent, H as PluginStorageConfig, J as ResolvedPlugin, K as RequestMeta, O as PageMetadataContribution, R as PluginDefinition, S as MediaItem$1, T as PageFragmentContribution, a as CommentAfterModerateEvent, k as PageMetadataEvent, m as EmailMessage, p as CronEvent, r as CommentAfterCreateEvent, s as CommentBeforeCreateEvent } from "./types-6dqxBqsH.mjs";
6
+ import { C as SiteSettingKey, w as SiteSettings } from "./validate-B7KP7VLM.mjs";
7
+ import { d as Storage } from "./types-BljtYPSd.mjs";
8
+ import { t as DatabaseDescriptor } from "./adapters-N6BF7RCD.mjs";
9
9
  import { Kysely } from "kysely";
10
10
  import { z } from "astro/zod";
11
11
  import { z as z$1 } from "zod";
@@ -120,6 +120,11 @@ declare class ContentRepository {
120
120
  authorId?: string;
121
121
  locale?: string;
122
122
  }): Promise<number>;
123
+ getStats(type: string): Promise<{
124
+ total: number;
125
+ published: number;
126
+ draft: number;
127
+ }>;
123
128
  /**
124
129
  * Schedule content for future publishing
125
130
  *
@@ -555,6 +560,8 @@ declare function handleContentCreate(db: Kysely<Database>, collection: string, b
555
560
  locale?: string;
556
561
  translationOf?: string;
557
562
  seo?: ContentSeoInput;
563
+ createdAt?: string | null;
564
+ publishedAt?: string | null;
558
565
  }): Promise<ApiResult<ContentResponse>>;
559
566
  /**
560
567
  * Update content item.
@@ -2339,17 +2346,19 @@ interface EmDashConfig {
2339
2346
  */
2340
2347
  marketplace?: string;
2341
2348
  /**
2342
- * Public browser origin for passkey verification (rpId + expected WebAuthn origin).
2349
+ * Public browser-facing origin for the site.
2343
2350
  *
2344
2351
  * Use when `Astro.url` / `request.url` do not match what users open — common with a
2345
2352
  * **TLS-terminating reverse proxy**: the app often sees `http://` on the internal hop
2346
- * while the browser uses `https://`, which breaks WebAuthn origin checks.
2353
+ * while the browser uses `https://`, which breaks WebAuthn, CSRF, OAuth, and redirect URLs.
2347
2354
  *
2348
2355
  * Set to the full origin users type in the address bar (no path), e.g.
2349
- * `https://emdash.local:8443`. Prefer fixing `security.allowedDomains` for the proxy first;
2350
- * use this when the reconstructed URL still diverges from the browser.
2356
+ * `https://mysite.example.com`. When not set, falls back to environment variables
2357
+ * `EMDASH_SITE_URL` > `SITE_URL`, then to the request URL's origin.
2358
+ *
2359
+ * Replaces `passkeyPublicOrigin` (which only fixed passkeys).
2351
2360
  */
2352
- passkeyPublicOrigin?: string;
2361
+ siteUrl?: string;
2353
2362
  /**
2354
2363
  * Enable playground mode for ephemeral "try EmDash" sites.
2355
2364
  *
@@ -2547,14 +2556,14 @@ declare const pluginManifestSchema: z$1.ZodObject<{
2547
2556
  string: "string";
2548
2557
  number: "number";
2549
2558
  boolean: "boolean";
2550
- file: "file";
2551
- select: "select";
2552
2559
  text: "text";
2553
2560
  integer: "integer";
2554
2561
  datetime: "datetime";
2562
+ select: "select";
2555
2563
  multiSelect: "multiSelect";
2556
2564
  portableText: "portableText";
2557
2565
  image: "image";
2566
+ file: "file";
2558
2567
  reference: "reference";
2559
2568
  json: "json";
2560
2569
  slug: "slug";
@@ -3033,6 +3042,11 @@ declare class PluginManager {
3033
3042
  private factoryOptions;
3034
3043
  private initialized;
3035
3044
  constructor(options: PluginManagerOptions);
3045
+ /**
3046
+ * Set the email pipeline used when creating plugin contexts.
3047
+ * Reinitializes routes/hooks if already initialized so ctx.email is available immediately.
3048
+ */
3049
+ setEmailPipeline(pipeline: EmailPipeline): void;
3036
3050
  /**
3037
3051
  * Register a plugin definition
3038
3052
  * This resolves the definition and adds it to the manager, but doesn't install it
@@ -3648,6 +3662,16 @@ declare function clearSources(): void;
3648
3662
  //#endregion
3649
3663
  //#region src/import/sources/wxr.d.ts
3650
3664
  declare const wxrSource: ImportSource;
3665
+ /**
3666
+ * Parse a WXR date with the correct fallback chain:
3667
+ * 1. GMT date (always UTC, most reliable)
3668
+ * 2. pubDate (RFC 2822, includes timezone offset)
3669
+ * 3. Site-local date (MySQL datetime without timezone, imprecise but best available)
3670
+ *
3671
+ * Returns undefined when none of the inputs yield a valid date.
3672
+ * Callers that need a guaranteed Date should use `?? new Date()`.
3673
+ */
3674
+ declare function parseWxrDate(gmtDate: string | undefined, pubDate: string | undefined, localDate: string | undefined): Date | undefined;
3651
3675
  //#endregion
3652
3676
  //#region src/import/sources/wordpress-rest.d.ts
3653
3677
  declare const wordpressRestSource: ImportSource;
@@ -3903,6 +3927,20 @@ declare function getSiteSettings(): Promise<Partial<SiteSettings>>;
3903
3927
  * ```
3904
3928
  */
3905
3929
  declare function setSiteSettings(settings: Partial<SiteSettings>, db: Kysely<Database>): Promise<void>;
3930
+ /**
3931
+ * Get a single plugin setting by key.
3932
+ *
3933
+ * Plugin settings are stored in the options table under
3934
+ * `plugin:<pluginId>:settings:<key>`.
3935
+ */
3936
+ declare function getPluginSetting<T = unknown>(pluginId: string, key: string): Promise<T | undefined>;
3937
+ /**
3938
+ * Get all persisted plugin settings for a plugin.
3939
+ *
3940
+ * Defaults declared in `admin.settingsSchema` are not materialized
3941
+ * automatically; callers should apply their own fallback defaults.
3942
+ */
3943
+ declare function getPluginSettings(pluginId: string): Promise<Record<string, unknown>>;
3906
3944
  //#endregion
3907
3945
  //#region src/comments/query.d.ts
3908
3946
  interface GetCommentsOptions {
@@ -4554,5 +4592,5 @@ declare function extractPlainText(blocks: PortableTextBlock$1[] | string | null
4554
4592
  */
4555
4593
  declare function extractSearchableFields(entry: Record<string, unknown>, fields: string[]): Record<string, string>;
4556
4594
  //#endregion
4557
- export { GetCommentsOptions as $, CMSAnnotation as $n, handleContentCreate as $r, PluginManager as $t, getEntryTerms as A, MediaItem as Ai, EntryFilter as An, GetSectionsOptions as Ar, FetchOptions as At, UpdateTermInput as B, ContentEntry as Bn, handleMediaDelete as Br, OAuthInput as Bt, ReorderWidgetsInput as C, image as Ci, WxrPost as Cn, SandboxRunner as Cr, getSource as Ct, WidgetComponentDef as D, ImageValue as Di, parseWxrString as Dn, getSection as Dr, importReusableBlocksAsSections as Dt, WidgetArea as E, FileValue as Ei, parseWxr as En, SerializedRequest as Er, registerSource as Et, getTermsForEntries as F, getI18nConfig as Fn, SchemaError as Fr, ImportContext as Ft, CreateMenuInput as G, ResolvePathResult as Gn, RevisionResponse as Gr, SourceCapabilities as Gt, getBylineBySlug as H, EmDashCollections as Hn, handleMediaList as Hr, PostTypeMapping as Ht, CreateTermInput as I, isI18nEnabled as In, SchemaRegistry as Ir, ImportFieldDef as It, MenuItem as J, getEditMeta as Jn, handleRevisionRestore as Jr, SuggestedAction as Jt, CreateMenuItemInput as K, TranslationSummary as Kn, handleRevisionGet as Kr, SourceInput as Kt, TaxonomyDef as L, CacheHint as Ln, MediaListResponse as Lr, ImportResult as Lt, getTaxonomyDefs as M, ContentRepository as Mi, getDb as Mn, SectionSource as Mr, FileInput as Mt, getTaxonomyTerms as N, DatabaseConfig as Ni, I18nConfig as Nn, UpdateSectionInput as Nr, ImportAnalysis as Nt, WidgetType as O, PortableTextBlock$2 as Oi, CollectionFilter as On, getSections as Or, AttachmentInfo as Ot, getTerm as P, EmDashDatabaseError as Pi, getFallbackChain as Pn, getCollectionInfo as Pr, ImportConfig as Pt, UpdateMenuItemInput as Q, resolveEmDashPath as Qn, handleContentCountTrashed as Qr, createNoopSandboxRunner as Qt, TaxonomyTerm as R, CollectionFilter$1 as Rn, MediaResponse as Rr, ImportSource as Rt, PropDef as S, reference as Si, WxrData as Sn, SandboxOptions as Sr, getFileSources as St, Widget as T, FieldUIHints as Ti, WxrTag as Tn, SandboxedPlugin as Tr, probeUrl as Tt, getMenu as U, EntryResult as Un, handleMediaUpdate as Ur, ProbeResult as Ut, getByline as V, EditFieldMeta as Vn, handleMediaGet as Vr, PostTypeAnalysis as Vt, getMenus as W, InferCollectionData as Wn, RevisionListResponse as Wr, SourceAuth as Wt, ReorderMenuItemsInput as X, getEmDashEntry as Xn, handleContentCompare as Xr, NoopSandboxRunner as Xt, MenuItemType as Y, getEmDashCollection as Yn, generateManifest as Yr, UrlInput as Yt, UpdateMenuInput as Z, getTranslations as Zn, handleContentCountScheduled as Zr, SandboxNotAvailableError as Zt, getWidgetArea as _, ContentResponse as _i, ExternalAuthConfig as _n, ProseMirrorDocument as _r, verifyPreviewToken as _t, search as a, handleContentList as ai, createHookPipeline as an, sanitizeHref as ar, setSiteSettings as at, CreateWidgetAreaInput as b, ManifestResponse as bi, WxrAuthor as bn, SandboxEmailMessage as br, clearSources as bt, FTSManager as c, handleContentPublish as ci, EmDashConfig as cn, portableTextToProsemirror as cr, GetPreviewUrlOptions as ct, SearchOptions as d, handleContentTranslations as di, LocalStorageConfig as dn, PortableTextImageBlock as dr, GeneratePreviewTokenOptions as dt, handleContentDelete as ei, createPluginManager as en, EditProxy as er, GetCommentsResult as et, SearchResponse as f, handleContentUnpublish as fi, S3StorageConfig as fn, PortableTextLinkMark as fr, PreviewTokenPayload as ft, Suggestion as g, ContentListResponse as gi, AuthResult as gn, PortableTextUnknownBlock as gr, parseContentId as gt, SuggestOptions as h, ApiContext as hi, AuthProviderModule as hn, PortableTextTextBlock as hr, generatePreviewToken as ht, getSuggestions as i, handleContentGetIncludingTrashed as ii, HookResult as in, isSafeHref as ir, getSiteSettings as it, getTaxonomyDef as j, MediaRepository as ji, emdashLoader as jn, Section as jr, FieldCompatibility as jt, getEntriesByTerm as k, CreateMediaInput as ki, EntryData as kn, CreateSectionInput as kr, CollectionSchemaStatus as kt, CollectionSearchOptions as l, handleContentRestore as li, PluginDescriptor as ln, prosemirrorToPortableText as lr, buildPreviewUrl as lt, SearchStats as m, handleContentUpdate as mi, AuthDescriptor as mn, PortableTextSpan as mr, VerifyPreviewTokenResult as mt, extractSearchableFields as n, handleContentDuplicate as ni, EmailPipeline as nn, createEditable as nr, getComments as nt, searchCollection as o, handleContentListTrashed as oi, ValidatedPluginManifest as on, computeContentHash as or, getPreviewToken as ot, SearchResult as p, handleContentUnschedule as pi, StorageDescriptor as pn, PortableTextMarkDef as pr, VerifyPreviewTokenOptions as pt, Menu as q, TranslationsResult as qn, handleRevisionList as qr, SourceProbeResult as qt, getSearchStats as r, handleContentGet as ri, HookPipeline as rn, createNoop as rr, getSiteSetting as rt, searchWithDb as s, handleContentPermanentDelete as si, pluginManifestSchema as sn, hashString as sr, isPreviewRequest as st, extractPlainText as t, handleContentDiscardDraft as ti, PluginRouteError as tn, FieldAnnotation as tr, getCommentCount as tt, SearchConfig as u, handleContentSchedule as ui, getStoredConfig as un, PortableTextCodeBlock as ur, getPreviewUrl as ut, getWidgetAreas as v, FieldDescriptor as vi, definePlugin as vn, ProseMirrorMark as vr, wordpressRestSource as vt, UpdateWidgetInput as w, FieldDefinition as wi, WxrSite as wn, SandboxRunnerFactory as wr, getUrlSources as wt, CreateWidgetInput as x, portableText as xi, WxrCategory as xn, SandboxEmailSendCallback as xr, getAllSources as xt, getWidgetComponents as y, ListResponse as yi, WxrAttachment as yn, ProseMirrorNode as yr, wxrSource as yt, TaxonomyTermRow as z, CollectionResult as zn, handleMediaCreate as zr, NormalizedItem as zt };
4558
- //# sourceMappingURL=index-CLBc4gw-.d.mts.map
4595
+ export { GetCommentsOptions as $, getEmDashEntry as $n, handleContentCompare as $r, NoopSandboxRunner as $t, getEntryTerms as A, ImageValue as Ai, parseWxrString as An, getSection as Ar, importReusableBlocksAsSections as At, UpdateTermInput as B, CacheHint as Bn, MediaListResponse as Br, ImportResult as Bt, ReorderWidgetsInput as C, ManifestResponse as Ci, WxrAuthor as Cn, SandboxEmailMessage as Cr, clearSources as Ct, WidgetComponentDef as D, FieldDefinition as Di, WxrSite as Dn, SandboxRunnerFactory as Dr, getUrlSources as Dt, WidgetArea as E, image as Ei, WxrPost as En, SandboxRunner as Er, getSource as Et, getTermsForEntries as F, ContentRepository as Fi, getDb as Fn, SectionSource as Fr, FileInput as Ft, CreateMenuInput as G, EmDashCollections as Gn, handleMediaList as Gr, PostTypeMapping as Gt, getBylineBySlug as H, CollectionResult as Hn, handleMediaCreate as Hr, NormalizedItem as Ht, CreateTermInput as I, DatabaseConfig as Ii, I18nConfig as In, UpdateSectionInput as Ir, ImportAnalysis as It, MenuItem as J, ResolvePathResult as Jn, RevisionResponse as Jr, SourceCapabilities as Jt, CreateMenuItemInput as K, EntryResult as Kn, handleMediaUpdate as Kr, ProbeResult as Kt, TaxonomyDef as L, EmDashDatabaseError as Li, getFallbackChain as Ln, getCollectionInfo as Lr, ImportConfig as Lt, getTaxonomyDefs as M, CreateMediaInput as Mi, EntryData as Mn, CreateSectionInput as Mr, CollectionSchemaStatus as Mt, getTaxonomyTerms as N, MediaItem as Ni, EntryFilter as Nn, GetSectionsOptions as Nr, FetchOptions as Nt, WidgetType as O, FieldUIHints as Oi, WxrTag as On, SandboxedPlugin as Or, probeUrl as Ot, getTerm as P, MediaRepository as Pi, emdashLoader as Pn, Section as Pr, FieldCompatibility as Pt, UpdateMenuItemInput as Q, getEmDashCollection as Qn, generateManifest as Qr, UrlInput as Qt, TaxonomyTerm as R, getI18nConfig as Rn, SchemaError as Rr, ImportContext as Rt, PropDef as S, ListResponse as Si, WxrAttachment as Sn, ProseMirrorNode as Sr, wxrSource as St, Widget as T, reference as Ti, WxrData as Tn, SandboxOptions as Tr, getFileSources as Tt, getMenu as U, ContentEntry as Un, handleMediaDelete as Ur, OAuthInput as Ut, getByline as V, CollectionFilter$1 as Vn, MediaResponse as Vr, ImportSource as Vt, getMenus as W, EditFieldMeta as Wn, handleMediaGet as Wr, PostTypeAnalysis as Wt, ReorderMenuItemsInput as X, TranslationsResult as Xn, handleRevisionList as Xr, SourceProbeResult as Xt, MenuItemType as Y, TranslationSummary as Yn, handleRevisionGet as Yr, SourceInput as Yt, UpdateMenuInput as Z, getEditMeta as Zn, handleRevisionRestore as Zr, SuggestedAction as Zt, getWidgetArea as _, handleContentUpdate as _i, AuthDescriptor as _n, PortableTextSpan as _r, generatePreviewToken as _t, search as a, handleContentDuplicate as ai, EmailPipeline as an, createEditable as ar, getSiteSetting as at, CreateWidgetAreaInput as b, ContentResponse as bi, ExternalAuthConfig as bn, ProseMirrorDocument as br, wordpressRestSource as bt, FTSManager as c, handleContentList as ci, createHookPipeline as cn, sanitizeHref as cr, getPreviewToken as ct, SearchOptions as d, handleContentPublish as di, EmDashConfig as dn, portableTextToProsemirror as dr, buildPreviewUrl as dt, handleContentCountScheduled as ei, SandboxNotAvailableError as en, getTranslations as er, GetCommentsResult as et, SearchResponse as f, handleContentRestore as fi, PluginDescriptor as fn, prosemirrorToPortableText as fr, getPreviewUrl as ft, Suggestion as g, handleContentUnschedule as gi, StorageDescriptor as gn, PortableTextMarkDef as gr, VerifyPreviewTokenResult as gt, SuggestOptions as h, handleContentUnpublish as hi, S3StorageConfig as hn, PortableTextLinkMark as hr, VerifyPreviewTokenOptions as ht, getSuggestions as i, handleContentDiscardDraft as ii, PluginRouteError as in, FieldAnnotation as ir, getPluginSettings as it, getTaxonomyDef as j, PortableTextBlock$2 as ji, CollectionFilter as jn, getSections as jr, AttachmentInfo as jt, getEntriesByTerm as k, FileValue as ki, parseWxr as kn, SerializedRequest as kr, registerSource as kt, CollectionSearchOptions as l, handleContentListTrashed as li, ValidatedPluginManifest as ln, computeContentHash as lr, isPreviewRequest as lt, SearchStats as m, handleContentTranslations as mi, LocalStorageConfig as mn, PortableTextImageBlock as mr, PreviewTokenPayload as mt, extractSearchableFields as n, handleContentCreate as ni, PluginManager as nn, CMSAnnotation as nr, getComments as nt, searchCollection as o, handleContentGet as oi, HookPipeline as on, createNoop as or, getSiteSettings as ot, SearchResult as p, handleContentSchedule as pi, getStoredConfig as pn, PortableTextCodeBlock as pr, GeneratePreviewTokenOptions as pt, Menu as q, InferCollectionData as qn, RevisionListResponse as qr, SourceAuth as qt, getSearchStats as r, handleContentDelete as ri, createPluginManager as rn, EditProxy as rr, getPluginSetting as rt, searchWithDb as s, handleContentGetIncludingTrashed as si, HookResult as sn, isSafeHref as sr, setSiteSettings as st, extractPlainText as t, handleContentCountTrashed as ti, createNoopSandboxRunner as tn, resolveEmDashPath as tr, getCommentCount as tt, SearchConfig as u, handleContentPermanentDelete as ui, pluginManifestSchema as un, hashString as ur, GetPreviewUrlOptions as ut, getWidgetAreas as v, ApiContext as vi, AuthProviderModule as vn, PortableTextTextBlock as vr, parseContentId as vt, UpdateWidgetInput as w, portableText as wi, WxrCategory as wn, SandboxEmailSendCallback as wr, getAllSources as wt, CreateWidgetInput as x, FieldDescriptor as xi, definePlugin as xn, ProseMirrorMark as xr, parseWxrDate as xt, getWidgetComponents as y, ContentListResponse as yi, AuthResult as yn, PortableTextUnknownBlock as yr, verifyPreviewToken as yt, TaxonomyTermRow as z, isI18nEnabled as zn, SchemaRegistry as zr, ImportFieldDef as zt };
4596
+ //# sourceMappingURL=index-UHEVQMus.d.mts.map