@velora-cms/server 0.11.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 (367) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +65 -0
  3. package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js_commonjs-proxy-Ng0fOpdU.js +1 -0
  4. package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js_commonjs-proxy-oc1i_iBU.js +9 -0
  5. package/dist/admin-bundle/assets/_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js +1 -0
  6. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-C1-ZAxwO.js +2 -0
  7. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js-BuUBskgj.js +9 -0
  8. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DMaku-HC.js +9 -0
  9. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js-DYfqn12F.js +1 -0
  10. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-BPoj3DC9.js +25 -0
  11. package/dist/admin-bundle/assets/content-trash.schema-DfXHkC-w.js +72 -0
  12. package/dist/admin-bundle/assets/hostInit--C4xK6nj.js +1 -0
  13. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  14. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  15. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  16. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  17. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  18. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  19. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  20. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  21. package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  22. package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  23. package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  24. package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  25. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  26. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  27. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  28. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  29. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  30. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  31. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  32. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  33. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-CyU-ddYS.woff2 +0 -0
  34. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-EA1o-u_Q.woff +0 -0
  35. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-C4MQITzh.woff2 +0 -0
  36. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-DPXcppbm.woff +0 -0
  37. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-0pRdybE_.woff2 +0 -0
  38. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-MJZ0liF5.woff +0 -0
  39. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-Bo5KPYvw.woff2 +0 -0
  40. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-CFZh1unN.woff +0 -0
  41. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-BletK1Hp.woff +0 -0
  42. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-Cd6jVIg7.woff2 +0 -0
  43. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-Dk5eISD_.woff +0 -0
  44. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-KrqB56Mw.woff2 +0 -0
  45. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BeVLg08x.woff +0 -0
  46. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BpLfq-63.woff2 +0 -0
  47. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-Ck_W4G78.woff2 +0 -0
  48. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-eJxPnL1P.woff +0 -0
  49. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DT29Q8HW.woff +0 -0
  50. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DjDAeNQD.woff2 +0 -0
  51. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
  52. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
  53. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
  54. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
  55. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
  56. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
  57. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
  58. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
  59. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
  60. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
  61. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
  62. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
  63. package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
  64. package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
  65. package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
  66. package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
  67. package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
  68. package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
  69. package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  70. package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  71. package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  72. package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  73. package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  74. package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  75. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
  76. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
  77. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
  78. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
  79. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
  80. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
  81. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
  82. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
  83. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
  84. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
  85. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
  86. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
  87. package/dist/admin-bundle/assets/index-BIAF0ShP.js +542 -0
  88. package/dist/admin-bundle/assets/index-DhMY-0sC.js +148 -0
  89. package/dist/admin-bundle/assets/index-DnZV_gsg.css +1 -0
  90. package/dist/admin-bundle/assets/index-Do71v6NZ.js +7 -0
  91. package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-italic-5yUt4G_9.woff2 +0 -0
  92. package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-normal-C5tfwALZ.woff2 +0 -0
  93. package/dist/admin-bundle/assets/literata-cyrillic-opsz-italic-Bj0ndbyp.woff2 +0 -0
  94. package/dist/admin-bundle/assets/literata-cyrillic-opsz-normal-DgmypKXX.woff2 +0 -0
  95. package/dist/admin-bundle/assets/literata-greek-ext-opsz-italic-DFpO5j-Z.woff2 +0 -0
  96. package/dist/admin-bundle/assets/literata-greek-ext-opsz-normal-DGm64vGR.woff2 +0 -0
  97. package/dist/admin-bundle/assets/literata-greek-opsz-italic-CHERNRFq.woff2 +0 -0
  98. package/dist/admin-bundle/assets/literata-greek-opsz-normal-DFglUmAd.woff2 +0 -0
  99. package/dist/admin-bundle/assets/literata-latin-ext-opsz-italic-4y8iAqqs.woff2 +0 -0
  100. package/dist/admin-bundle/assets/literata-latin-ext-opsz-normal-STiTPs0F.woff2 +0 -0
  101. package/dist/admin-bundle/assets/literata-latin-opsz-italic-CJ93D38p.woff2 +0 -0
  102. package/dist/admin-bundle/assets/literata-latin-opsz-normal-BHZsWIlc.woff2 +0 -0
  103. package/dist/admin-bundle/assets/literata-vietnamese-opsz-italic-BdcrZMcO.woff2 +0 -0
  104. package/dist/admin-bundle/assets/literata-vietnamese-opsz-normal-Gqw5JQlq.woff2 +0 -0
  105. package/dist/admin-bundle/assets/mf-entry-bootstrap-0-aef015cd.js +41 -0
  106. package/dist/admin-bundle/assets/remoteEntry-CNyN68bc.js +14 -0
  107. package/dist/admin-bundle/assets/virtualExposes-BDCSo5OS.js +1 -0
  108. package/dist/admin-bundle/assets/vite-preload-helper-Cgledu0r.js +1 -0
  109. package/dist/admin-bundle/index.html +16 -0
  110. package/dist/app.d.ts +8 -0
  111. package/dist/app.js +252 -0
  112. package/dist/auth/api-content-author.d.ts +2 -0
  113. package/dist/auth/api-content-author.js +27 -0
  114. package/dist/auth/api-keys.d.ts +7 -0
  115. package/dist/auth/api-keys.js +85 -0
  116. package/dist/auth/jwt-payload.d.ts +16 -0
  117. package/dist/auth/jwt-payload.js +1 -0
  118. package/dist/auth/preview-tokens.d.ts +7 -0
  119. package/dist/auth/preview-tokens.js +76 -0
  120. package/dist/auth/rate-limit.d.ts +16 -0
  121. package/dist/auth/rate-limit.js +283 -0
  122. package/dist/auth/require-auth.d.ts +4 -0
  123. package/dist/auth/require-auth.js +28 -0
  124. package/dist/auth/route-rate-limits.d.ts +31 -0
  125. package/dist/auth/route-rate-limits.js +155 -0
  126. package/dist/auth/tokens.d.ts +15 -0
  127. package/dist/auth/tokens.js +38 -0
  128. package/dist/bin.d.ts +2 -0
  129. package/dist/bin.js +6 -0
  130. package/dist/boot/core-migration-gate.d.ts +12 -0
  131. package/dist/boot/core-migration-gate.js +52 -0
  132. package/dist/boot/resolve-boot-mode.d.ts +6 -0
  133. package/dist/boot/resolve-boot-mode.js +206 -0
  134. package/dist/bundled-templates/com.velora.blog/manifest.json +39 -0
  135. package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
  136. package/dist/bundled-templates/com.velora.blog/sample-content.json +14 -0
  137. package/dist/config/admin-ui.d.ts +1 -0
  138. package/dist/config/admin-ui.js +10 -0
  139. package/dist/config/cors.d.ts +7 -0
  140. package/dist/config/cors.js +53 -0
  141. package/dist/config/env-file-path.d.ts +2 -0
  142. package/dist/config/env-file-path.js +22 -0
  143. package/dist/config/graphql-limits.d.ts +2 -0
  144. package/dist/config/graphql-limits.js +26 -0
  145. package/dist/config/headless.d.ts +1 -0
  146. package/dist/config/headless.js +8 -0
  147. package/dist/config/license-revalidation.d.ts +2 -0
  148. package/dist/config/license-revalidation.js +16 -0
  149. package/dist/config/load-env.d.ts +1 -0
  150. package/dist/config/load-env.js +53 -0
  151. package/dist/config/marketplace.d.ts +11 -0
  152. package/dist/config/marketplace.js +19 -0
  153. package/dist/config/preview.d.ts +3 -0
  154. package/dist/config/preview.js +39 -0
  155. package/dist/config/public-docs.d.ts +1 -0
  156. package/dist/config/public-docs.js +9 -0
  157. package/dist/config/site.d.ts +7 -0
  158. package/dist/config/site.js +12 -0
  159. package/dist/config/trust-proxy.d.ts +2 -0
  160. package/dist/config/trust-proxy.js +42 -0
  161. package/dist/data-types/validate-data-type-settings.d.ts +1 -0
  162. package/dist/data-types/validate-data-type-settings.js +33 -0
  163. package/dist/db/content-db.d.ts +16 -0
  164. package/dist/db/content-db.js +109 -0
  165. package/dist/db/core-migrations/index.d.ts +14 -0
  166. package/dist/db/core-migrations/index.js +184 -0
  167. package/dist/db/core-migrations/steps.d.ts +4 -0
  168. package/dist/db/core-migrations/steps.js +66 -0
  169. package/dist/db/load-prisma-client.d.ts +2 -0
  170. package/dist/db/load-prisma-client.js +44 -0
  171. package/dist/db/prisma.d.ts +3 -0
  172. package/dist/db/prisma.js +67 -0
  173. package/dist/db/resolve-client-dir.d.ts +2 -0
  174. package/dist/db/resolve-client-dir.js +83 -0
  175. package/dist/docs/filter-public-api-spec.d.ts +2 -0
  176. package/dist/docs/filter-public-api-spec.js +52 -0
  177. package/dist/docs-bundle/404.html +41 -0
  178. package/dist/docs-bundle/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js +1 -0
  179. package/dist/docs-bundle/_astro/Search.astro_astro_type_script_index_0_lang.CavldbAs.js +1 -0
  180. package/dist/docs-bundle/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js +1 -0
  181. package/dist/docs-bundle/_astro/common.DTXA7sXM.css +3 -0
  182. package/dist/docs-bundle/_astro/ec.0vx5m.js +3 -0
  183. package/dist/docs-bundle/_astro/ec.w36nc.css +1 -0
  184. package/dist/docs-bundle/_astro/page.Dwipeu-R.js +1 -0
  185. package/dist/docs-bundle/_astro/print.ehPL0gv-.css +1 -0
  186. package/dist/docs-bundle/_astro/starlight-toc.CTCQXEge.js +1 -0
  187. package/dist/docs-bundle/_astro/ui-core.D-7Scnhs.js +2 -0
  188. package/dist/docs-bundle/favicon.svg +1 -0
  189. package/dist/docs-bundle/index.html +1 -0
  190. package/dist/docs-bundle/pagefind/fragment/en_18a36d7.pf_fragment +0 -0
  191. package/dist/docs-bundle/pagefind/fragment/en_1d9eb24.pf_fragment +0 -0
  192. package/dist/docs-bundle/pagefind/fragment/en_1ef320c.pf_fragment +0 -0
  193. package/dist/docs-bundle/pagefind/fragment/en_3099406.pf_fragment +0 -0
  194. package/dist/docs-bundle/pagefind/fragment/en_4a5b6fe.pf_fragment +0 -0
  195. package/dist/docs-bundle/pagefind/fragment/en_9aa652e.pf_fragment +0 -0
  196. package/dist/docs-bundle/pagefind/fragment/en_baccdf6.pf_fragment +0 -0
  197. package/dist/docs-bundle/pagefind/fragment/en_d81f3fe.pf_fragment +0 -0
  198. package/dist/docs-bundle/pagefind/fragment/en_d855550.pf_fragment +0 -0
  199. package/dist/docs-bundle/pagefind/fragment/en_e6d88d8.pf_fragment +0 -0
  200. package/dist/docs-bundle/pagefind/fragment/en_e9d3189.pf_fragment +0 -0
  201. package/dist/docs-bundle/pagefind/index/en_5feeed7.pf_index +0 -0
  202. package/dist/docs-bundle/pagefind/index/en_c6981f9.pf_index +0 -0
  203. package/dist/docs-bundle/pagefind/index/en_ce4f6bf.pf_index +0 -0
  204. package/dist/docs-bundle/pagefind/pagefind-component-ui.css +1509 -0
  205. package/dist/docs-bundle/pagefind/pagefind-component-ui.js +56 -0
  206. package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -0
  207. package/dist/docs-bundle/pagefind/pagefind-highlight.js +1070 -0
  208. package/dist/docs-bundle/pagefind/pagefind-modular-ui.css +214 -0
  209. package/dist/docs-bundle/pagefind/pagefind-modular-ui.js +8 -0
  210. package/dist/docs-bundle/pagefind/pagefind-ui.css +1 -0
  211. package/dist/docs-bundle/pagefind/pagefind-ui.js +2 -0
  212. package/dist/docs-bundle/pagefind/pagefind-worker.js +6 -0
  213. package/dist/docs-bundle/pagefind/pagefind.en_1e94abd852.pf_meta +0 -0
  214. package/dist/docs-bundle/pagefind/pagefind.js +6 -0
  215. package/dist/docs-bundle/pagefind/wasm.en.pagefind +0 -0
  216. package/dist/docs-bundle/pagefind/wasm.unknown.pagefind +0 -0
  217. package/dist/docs-bundle/v1/index.html +86 -0
  218. package/dist/docs-bundle/v1/using-velora/build-a-headless-frontend/index.html +413 -0
  219. package/dist/docs-bundle/v1/using-velora/content-model/index.html +858 -0
  220. package/dist/docs-bundle/v1/using-velora/editing/index.html +748 -0
  221. package/dist/docs-bundle/v1/using-velora/getting-started/index.html +588 -0
  222. package/dist/docs-bundle/v1/using-velora/headless-api/index.html +1620 -0
  223. package/dist/docs-bundle/v1/using-velora/index.html +215 -0
  224. package/dist/docs-bundle/v1/using-velora/installation/index.html +886 -0
  225. package/dist/docs-bundle/v1/using-velora/installing-plugins/index.html +836 -0
  226. package/dist/docs-bundle/v1/using-velora/media/index.html +468 -0
  227. package/dist/docs-bundle/v1/using-velora/users-and-roles/index.html +561 -0
  228. package/dist/graphql/field-count-rule.d.ts +5 -0
  229. package/dist/graphql/field-count-rule.js +59 -0
  230. package/dist/graphql/schema.d.ts +12 -0
  231. package/dist/graphql/schema.js +330 -0
  232. package/dist/index.d.ts +1 -0
  233. package/dist/index.js +180 -0
  234. package/dist/lib.d.ts +2 -0
  235. package/dist/lib.js +39 -0
  236. package/dist/marketplace/install-bundle-members.d.ts +33 -0
  237. package/dist/marketplace/install-bundle-members.js +137 -0
  238. package/dist/marketplace/install-from-marketplace.d.ts +60 -0
  239. package/dist/marketplace/install-from-marketplace.js +581 -0
  240. package/dist/marketplace/license-revalidation-job.d.ts +38 -0
  241. package/dist/marketplace/license-revalidation-job.js +139 -0
  242. package/dist/marketplace/marketplace-client.d.ts +43 -0
  243. package/dist/marketplace/marketplace-client.js +228 -0
  244. package/dist/marketplace/revalidate-license.d.ts +39 -0
  245. package/dist/marketplace/revalidate-license.js +103 -0
  246. package/dist/media/media-service.d.ts +36 -0
  247. package/dist/media/media-service.js +162 -0
  248. package/dist/plugins/boot-plugin-loader.d.ts +32 -0
  249. package/dist/plugins/boot-plugin-loader.js +57 -0
  250. package/dist/plugins/hook-dispatcher.d.ts +13 -0
  251. package/dist/plugins/hook-dispatcher.js +68 -0
  252. package/dist/plugins/install-built-package.d.ts +36 -0
  253. package/dist/plugins/install-built-package.js +199 -0
  254. package/dist/plugins/install-package-tarball.d.ts +25 -0
  255. package/dist/plugins/install-package-tarball.js +73 -0
  256. package/dist/plugins/plugin-context.d.ts +5 -0
  257. package/dist/plugins/plugin-context.js +26 -0
  258. package/dist/plugins/plugin-package-store.d.ts +31 -0
  259. package/dist/plugins/plugin-package-store.js +64 -0
  260. package/dist/plugins/plugin-registry-service.d.ts +227 -0
  261. package/dist/plugins/plugin-registry-service.js +636 -0
  262. package/dist/plugins/seed-template-content.d.ts +23 -0
  263. package/dist/plugins/seed-template-content.js +198 -0
  264. package/dist/plugins/server-serializers.d.ts +3 -0
  265. package/dist/plugins/server-serializers.js +16 -0
  266. package/dist/plugins/simple-storage-api.d.ts +9 -0
  267. package/dist/plugins/simple-storage-api.js +26 -0
  268. package/dist/plugins/snapshot-groups.d.ts +16 -0
  269. package/dist/plugins/snapshot-groups.js +81 -0
  270. package/dist/plugins/structured-query-builder.d.ts +21 -0
  271. package/dist/plugins/structured-query-builder.js +60 -0
  272. package/dist/plugins/structured-snapshot-service.d.ts +16 -0
  273. package/dist/plugins/structured-snapshot-service.js +286 -0
  274. package/dist/plugins/structured-storage-api.d.ts +22 -0
  275. package/dist/plugins/structured-storage-api.js +91 -0
  276. package/dist/plugins/structured-storage-transaction.d.ts +22 -0
  277. package/dist/plugins/structured-storage-transaction.js +63 -0
  278. package/dist/plugins/structured-table-naming.d.ts +1 -0
  279. package/dist/plugins/structured-table-naming.js +18 -0
  280. package/dist/plugins/trusted-keys.d.ts +11 -0
  281. package/dist/plugins/trusted-keys.js +67 -0
  282. package/dist/plugins/trusted-signing-keys.d.ts +27 -0
  283. package/dist/plugins/trusted-signing-keys.js +40 -0
  284. package/dist/plugins/uninstall-bundle.d.ts +29 -0
  285. package/dist/plugins/uninstall-bundle.js +150 -0
  286. package/dist/plugins/verify-package.d.ts +36 -0
  287. package/dist/plugins/verify-package.js +121 -0
  288. package/dist/routes/admin-static.d.ts +8 -0
  289. package/dist/routes/admin-static.js +196 -0
  290. package/dist/routes/api-keys.d.ts +2 -0
  291. package/dist/routes/api-keys.js +95 -0
  292. package/dist/routes/api-playground.d.ts +2 -0
  293. package/dist/routes/api-playground.js +21 -0
  294. package/dist/routes/auth.d.ts +2 -0
  295. package/dist/routes/auth.js +102 -0
  296. package/dist/routes/content.d.ts +3 -0
  297. package/dist/routes/content.js +471 -0
  298. package/dist/routes/data-types.d.ts +3 -0
  299. package/dist/routes/data-types.js +100 -0
  300. package/dist/routes/docs-static.d.ts +8 -0
  301. package/dist/routes/docs-static.js +137 -0
  302. package/dist/routes/document-types.d.ts +3 -0
  303. package/dist/routes/document-types.js +149 -0
  304. package/dist/routes/headless-bridge.d.ts +10 -0
  305. package/dist/routes/headless-bridge.js +207 -0
  306. package/dist/routes/health.d.ts +2 -0
  307. package/dist/routes/health.js +15 -0
  308. package/dist/routes/marketplace-proxy.d.ts +8 -0
  309. package/dist/routes/marketplace-proxy.js +135 -0
  310. package/dist/routes/media.d.ts +6 -0
  311. package/dist/routes/media.js +93 -0
  312. package/dist/routes/plugin-api-routes.d.ts +12 -0
  313. package/dist/routes/plugin-api-routes.js +224 -0
  314. package/dist/routes/plugin-assets.d.ts +8 -0
  315. package/dist/routes/plugin-assets.js +51 -0
  316. package/dist/routes/plugin-management.d.ts +7 -0
  317. package/dist/routes/plugin-management.js +525 -0
  318. package/dist/routes/plugin-storage.d.ts +8 -0
  319. package/dist/routes/plugin-storage.js +104 -0
  320. package/dist/routes/plugins.d.ts +2 -0
  321. package/dist/routes/plugins.js +36 -0
  322. package/dist/routes/public-api.d.ts +9 -0
  323. package/dist/routes/public-api.js +545 -0
  324. package/dist/routes/published-item.d.ts +57 -0
  325. package/dist/routes/published-item.js +236 -0
  326. package/dist/routes/setup.d.ts +9 -0
  327. package/dist/routes/setup.js +477 -0
  328. package/dist/routes/snapshots.d.ts +3 -0
  329. package/dist/routes/snapshots.js +65 -0
  330. package/dist/routes/templates.d.ts +3 -0
  331. package/dist/routes/templates.js +37 -0
  332. package/dist/routes/themes.d.ts +2 -0
  333. package/dist/routes/themes.js +30 -0
  334. package/dist/routes/users.d.ts +2 -0
  335. package/dist/routes/users.js +113 -0
  336. package/dist/sandbox/register-local-plugin.d.ts +36 -0
  337. package/dist/sandbox/register-local-plugin.js +108 -0
  338. package/dist/sandbox/seed-sandbox.d.ts +20 -0
  339. package/dist/sandbox/seed-sandbox.js +122 -0
  340. package/dist/scripts/copy-admin-bundle.d.ts +1 -0
  341. package/dist/scripts/copy-admin-bundle.js +24 -0
  342. package/dist/scripts/copy-docs-bundle.d.ts +1 -0
  343. package/dist/scripts/copy-docs-bundle.js +22 -0
  344. package/dist/scripts/create-api-key.d.ts +5 -0
  345. package/dist/scripts/create-api-key.js +56 -0
  346. package/dist/scripts/generate-prisma-clients.d.ts +6 -0
  347. package/dist/scripts/generate-prisma-clients.js +125 -0
  348. package/dist/scripts/pack-bundled-templates.d.ts +1 -0
  349. package/dist/scripts/pack-bundled-templates.js +87 -0
  350. package/dist/setup/build-connection-string.d.ts +19 -0
  351. package/dist/setup/build-connection-string.js +14 -0
  352. package/dist/setup/bundled-templates.d.ts +19 -0
  353. package/dist/setup/bundled-templates.js +121 -0
  354. package/dist/setup/ensure-prisma-client.d.ts +3 -0
  355. package/dist/setup/ensure-prisma-client.js +23 -0
  356. package/dist/setup/install-starter.d.ts +14 -0
  357. package/dist/setup/install-starter.js +76 -0
  358. package/dist/setup/normalize-database-url.d.ts +2 -0
  359. package/dist/setup/normalize-database-url.js +35 -0
  360. package/dist/setup/persist-env-config.d.ts +2 -0
  361. package/dist/setup/persist-env-config.js +63 -0
  362. package/dist/setup/provision-database.d.ts +2 -0
  363. package/dist/setup/provision-database.js +89 -0
  364. package/dist/setup-app.d.ts +4 -0
  365. package/dist/setup-app.js +43 -0
  366. package/package.json +96 -0
  367. package/prisma/schema.prisma +303 -0
@@ -0,0 +1,36 @@
1
+ import { PluginListQuerySchema, PluginListResponseSchema } from "@velora-cms/api-schemas";
2
+ import { prisma } from "../db/prisma.js";
3
+ // Read-only, no auth (matches GET /api/data-types — this is metadata,
4
+ // not content). Always filters to enabled:true: an installed-but-inactive
5
+ // plugin isn't a candidate for the admin's boot-time remote loader, the
6
+ // only consumer today.
7
+ export const pluginRoutes = async (app) => {
8
+ app.get("/api/plugins", {
9
+ schema: {
10
+ querystring: PluginListQuerySchema,
11
+ response: {
12
+ 200: PluginListResponseSchema,
13
+ },
14
+ },
15
+ }, async (request) => {
16
+ const { cursor, limit } = request.query;
17
+ const rows = await prisma.pluginRegistry.findMany({
18
+ where: { enabled: true },
19
+ orderBy: { id: "asc" },
20
+ take: limit,
21
+ ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
22
+ });
23
+ const items = rows.map((row) => ({
24
+ pluginId: row.pluginId,
25
+ name: row.name,
26
+ type: row.type,
27
+ version: row.version,
28
+ trustTier: row.trustTier,
29
+ storageType: row.storageType,
30
+ enabled: row.enabled,
31
+ remoteUrl: row.remoteUrl,
32
+ }));
33
+ const nextCursor = items.length === limit ? (rows[rows.length - 1]?.id ?? null) : null;
34
+ return { items, nextCursor };
35
+ });
36
+ };
@@ -0,0 +1,9 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ import type { ContentDb } from "../db/content-db.js";
3
+ import type { MediaService } from "../media/media-service.js";
4
+ interface PublicApiRoutesOptions extends ContentDb {
5
+ mediaService: MediaService;
6
+ apiContentAuthorId: string;
7
+ }
8
+ export declare const publicApiRoutes: FastifyPluginAsyncZod<PublicApiRoutesOptions>;
9
+ export {};
@@ -0,0 +1,545 @@
1
+ import { createRequire } from "node:module";
2
+ import { Ajv } from "ajv";
3
+ import mercurius from "mercurius";
4
+ import { z } from "zod";
5
+ import { ApiErrorSchema, ContentValidationErrorSchema, PublicContentByPathQuerySchema, PublicContentFormatResponseSchema, PublicContentItemQuerySchema, PublicContentItemSchema, PublicContentListQuerySchema, PublicContentListResponseSchema, PublicContentSearchQuerySchema, PublicContentWriteResponseSchema, PublicCreateContentRequestSchema, PublicUpdateContentRequestSchema, PublicDocumentTypeListQuerySchema, PublicDocumentTypeListResponseSchema, PublicDocumentTypeSchema, PublicMediaFileSchema, PublicMediaListQuerySchema, PublicMediaListResponseSchema, PublicTemplateListQuerySchema, PublicTemplateListResponseSchema, } from "@velora-cms/api-schemas";
6
+ import { ContentValidationError, DocumentTypeNotFoundForContentError, ParentContentNotFoundError, TreeRulesViolationError, } from "@velora-cms/content-service";
7
+ import { requireApiKey, requireApiKeyScope } from "../auth/api-keys.js";
8
+ import { verifyPreviewToken } from "../auth/preview-tokens.js";
9
+ import { rateLimitConfig } from "../auth/rate-limit.js";
10
+ import { isPublicDocsEnabled } from "../config/public-docs.js";
11
+ import { maxQueryComplexity, maxQueryDepth } from "../config/graphql-limits.js";
12
+ import { createFieldCountRule } from "../graphql/field-count-rule.js";
13
+ import { buildGraphqlSchema } from "../graphql/schema.js";
14
+ import { computeExtensions, isNotFoundError, preparePublishedItem, resolveItemNode, resolveSubtreeFilter, toPublicDocumentType, toPublicMediaFile, toSummaries, } from "./published-item.js";
15
+ const PublicContentParamsSchema = z.object({ id: z.string().uuid() });
16
+ // mercurius' own /api/v1/graphql routes carry plain JSON-Schema objects for
17
+ // their request/response envelope (not Zod). This app's validatorCompiler/
18
+ // serializerCompiler are set globally to fastify-type-provider-zod's Zod
19
+ // compilers (app.ts), which unconditionally treat `schema` as a Zod object
20
+ // and mis-validate anything else — every mercurius request failed
21
+ // validation until these were wired in as a per-route override via
22
+ // mercurius' `additionalRouteOptions` (Session 113, Task 2). Plain `ajv`,
23
+ // already a direct dependency (see validate-data-type-settings.ts), stands
24
+ // in for Fastify's own default compiler here.
25
+ const mercuriusAjv = new Ajv({ allErrors: true, strict: false });
26
+ const mercuriusValidatorCompiler = ({ schema }) => {
27
+ const validate = mercuriusAjv.compile(schema);
28
+ return (data) => {
29
+ if (validate(data))
30
+ return { value: data };
31
+ return { error: new Error(mercuriusAjv.errorsText(validate.errors)) };
32
+ };
33
+ };
34
+ // fast-json-stringify isn't wired up for this one-off route; a plain
35
+ // JSON.stringify is a correct (if slower) response serializer, and
36
+ // response payloads here are small.
37
+ const mercuriusSerializerCompiler = () => {
38
+ return (data) => JSON.stringify(data);
39
+ };
40
+ // One uniform 404 body for every public miss — unknown id, unknown path,
41
+ // draft, unpublished translation. Distinguishing them would let a keyed
42
+ // caller probe which drafts exist.
43
+ const NOT_FOUND_MESSAGE = "No published content found";
44
+ // The public Content API namespace (Month 9). Registered by buildApp()
45
+ // only when VELORA_HEADLESS is on — when off, none of these routes exist.
46
+ // Every route is API-key gated (requireApiKey), never JWT. Strict
47
+ // published-only rule: every node this surface touches — the anchor of a
48
+ // locale resolution AND the resolved translation — must be published, so
49
+ // drafts are indistinguishable from nonexistent everywhere. Exception: the
50
+ // write routes (create/PUT/PATCH/publish, Session 107) operate on a node by
51
+ // id in any publish state — a key holder is a trusted writer editing their
52
+ // own draft, not an anonymous reader.
53
+ export const publicApiRoutes = async (app, opts) => {
54
+ await app.register(import("@fastify/rate-limit"), rateLimitConfig);
55
+ const { adapter, contentService, mediaService, apiContentAuthorId } = opts;
56
+ // Shared by the by-id and by-path routes: apply locale resolution (same
57
+ // contract as the backoffice — fallback is a locale string), enforce the
58
+ // published-only rule on the resolved node, and load its pointer version.
59
+ async function sendPublishedItem(request, reply, anchor, query, previewContentId) {
60
+ const node = await resolveItemNode({ adapter, contentService }, anchor, { locale: query.locale, fallback: query.fallback }, { previewContentId });
61
+ if (!node) {
62
+ return reply.code(404).send({ message: NOT_FOUND_MESSAGE });
63
+ }
64
+ // Passed in AFTER locale resolution, against the FINAL resolved
65
+ // node — `preparePublishedItem` itself only bypasses its gate when
66
+ // `node.id === previewContentId`, so a token for a different node
67
+ // (or no token) has no effect here.
68
+ const prepare = await preparePublishedItem({
69
+ adapter,
70
+ listSerializerContributions: () => app.pluginRegistry.listActiveSerializerContributions(),
71
+ warn: (obj, msg) => request.log.warn(obj, msg),
72
+ }, node, { previewContentId });
73
+ if (prepare.status === "notFound") {
74
+ return reply.code(404).send({ message: NOT_FOUND_MESSAGE });
75
+ }
76
+ if (prepare.status === "brokenVersion") {
77
+ return reply.code(500).send({ message: prepare.message });
78
+ }
79
+ const { core, data, contributions, buildSerializerContext } = prepare;
80
+ // ?format=<pluginId>:<name> — a whole-response projection owned by one
81
+ // plugin. Never falls back to the default shape on failure: the caller
82
+ // asked for a specific projection, so a missing/broken one is a 400.
83
+ if (query.format) {
84
+ const [pluginId, formatName] = query.format.split(":", 2);
85
+ const owner = contributions.find((c) => c.pluginId === pluginId);
86
+ const format = owner?.serializers.customFormats?.find((f) => f.name === formatName);
87
+ if (!format) {
88
+ return reply.code(400).send({ message: `Unknown format ${query.format}` });
89
+ }
90
+ try {
91
+ const content = await format.build(buildSerializerContext());
92
+ JSON.stringify(content); // S111-handoff guard: non-JSON output is a plugin bug, not a 500
93
+ return reply.send({ format: query.format, content });
94
+ }
95
+ catch (error) {
96
+ request.log.warn({ pluginId, format: formatName, err: error }, "custom format build failed");
97
+ return reply.code(400).send({ message: `Format ${query.format} failed to build` });
98
+ }
99
+ }
100
+ const extensions = await computeExtensions(prepare, (obj, msg) => request.log.warn(obj, msg));
101
+ return reply.send({
102
+ ...core,
103
+ data,
104
+ ...(extensions ? { extensions } : {}),
105
+ });
106
+ }
107
+ app.get("/api/v1/content/roots", {
108
+ onRequest: requireApiKey,
109
+ schema: {
110
+ querystring: PublicContentListQuerySchema,
111
+ response: { 200: PublicContentListResponseSchema, 401: ApiErrorSchema },
112
+ },
113
+ }, async (request) => {
114
+ const { cursor, limit } = request.query;
115
+ const items = await adapter.getRootNodes({ cursor, limit, publishedOnly: true });
116
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
117
+ return { items: toSummaries(items), nextCursor };
118
+ });
119
+ app.get("/api/v1/content/by-path", {
120
+ onRequest: requireApiKey,
121
+ schema: {
122
+ querystring: PublicContentByPathQuerySchema,
123
+ response: {
124
+ 200: z.union([PublicContentItemSchema, PublicContentFormatResponseSchema]),
125
+ 400: ApiErrorSchema,
126
+ 401: ApiErrorSchema,
127
+ 404: ApiErrorSchema,
128
+ 500: ApiErrorSchema,
129
+ },
130
+ },
131
+ }, async (request, reply) => {
132
+ const { path, ...query } = request.query;
133
+ // Session 116 (Task 2 fix): verified ONCE, up front, so it can also
134
+ // decide whether the anchor fetch itself may return a draft.
135
+ // `verifyPreviewToken` returns null for every failure mode (absent,
136
+ // malformed, tampered, expired), so an absent/invalid token
137
+ // collapses to `previewContentId: null` and the anchor fetch keeps
138
+ // its original `publishedOnly: true` — bit-for-bit today's
139
+ // behavior. A verified token lifts the adapter-level filter so a
140
+ // draft-only anchor can even be fetched; `resolveItemNode` and
141
+ // `preparePublishedItem` still do ALL the actual gating below (a
142
+ // token only ever unlocks the exact node it names).
143
+ const previewContentId = query.previewToken
144
+ ? (verifyPreviewToken(query.previewToken)?.contentId ?? null)
145
+ : null;
146
+ const anchor = previewContentId
147
+ ? await adapter.getContentNodeByPath(path)
148
+ : await adapter.getContentNodeByPath(path, { publishedOnly: true });
149
+ return sendPublishedItem(request, reply, anchor, query, previewContentId);
150
+ });
151
+ app.get("/api/v1/content", {
152
+ onRequest: requireApiKey,
153
+ schema: {
154
+ querystring: PublicContentSearchQuerySchema,
155
+ response: {
156
+ 200: PublicContentListResponseSchema,
157
+ 401: ApiErrorSchema,
158
+ 404: ApiErrorSchema,
159
+ },
160
+ },
161
+ }, async (request, reply) => {
162
+ const { documentTypeId, under, q, cursor, limit } = request.query;
163
+ let pathPrefix;
164
+ if (under) {
165
+ const resolved = await resolveSubtreeFilter(adapter, under);
166
+ if (!resolved) {
167
+ return reply.code(404).send({ message: NOT_FOUND_MESSAGE });
168
+ }
169
+ pathPrefix = resolved.pathPrefix;
170
+ }
171
+ const items = await adapter.searchPublishedContent({
172
+ cursor,
173
+ limit,
174
+ documentTypeId,
175
+ pathPrefix,
176
+ text: q,
177
+ });
178
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
179
+ return { items: toSummaries(items), nextCursor };
180
+ });
181
+ function toWriteResponse(node, version) {
182
+ return {
183
+ id: node.id,
184
+ documentTypeId: node.documentTypeId,
185
+ parentId: node.parentId,
186
+ path: node.path,
187
+ locale: node.locale,
188
+ version: version.version,
189
+ publishedAt: node.publishedAt,
190
+ data: version.data,
191
+ };
192
+ }
193
+ app.post("/api/v1/content", {
194
+ onRequest: [requireApiKey, requireApiKeyScope("write")],
195
+ schema: {
196
+ description: "Create a content node. Derives path and depth server-side from parentId + slug; sortOrder is fixed at 0 for API-created nodes. Non-empty `data` is fully field-validated against the document type (400 with fieldErrors); empty `data` ({}) creates a stub to fill in later via PATCH.",
197
+ body: PublicCreateContentRequestSchema,
198
+ response: {
199
+ 201: PublicContentWriteResponseSchema,
200
+ // Union so both bodies serialize on the one 400 slot: the
201
+ // structured field-validation body, and the plain-message body
202
+ // Fastify's own Zod body validation produces.
203
+ 400: z.union([ContentValidationErrorSchema, ApiErrorSchema]),
204
+ 401: ApiErrorSchema,
205
+ 403: ApiErrorSchema,
206
+ 404: ApiErrorSchema,
207
+ },
208
+ },
209
+ }, async (request, reply) => {
210
+ const { documentTypeId, parentId, slug, locale, isDefaultLocale, translationGroupId, data } = request.body;
211
+ // Derive path/depth from the parent (create derives tree internals so
212
+ // an external consumer never computes materialized-path values).
213
+ let path;
214
+ let depth;
215
+ if (parentId === null) {
216
+ path = `/${slug}`;
217
+ depth = 0;
218
+ }
219
+ else {
220
+ const parent = await adapter.getContentNode(parentId);
221
+ if (!parent) {
222
+ return reply.code(404).send({ message: `No content found for parent id ${parentId}` });
223
+ }
224
+ path = `${parent.path}/${slug}`;
225
+ depth = parent.depth + 1;
226
+ }
227
+ // Field validation of non-empty `data` lives in
228
+ // ContentService.createContent (S182), so the backoffice and public
229
+ // create paths share one rule; the ContentValidationError it throws
230
+ // is mapped to a 400 by the catch below.
231
+ try {
232
+ const { node, version } = await contentService.createContent({
233
+ documentTypeId,
234
+ parentId,
235
+ path,
236
+ depth,
237
+ sortOrder: 0,
238
+ locale,
239
+ isDefaultLocale,
240
+ ...(translationGroupId ? { translationGroupId } : {}),
241
+ data,
242
+ createdBy: apiContentAuthorId,
243
+ });
244
+ return reply.code(201).send(toWriteResponse(node, version));
245
+ }
246
+ catch (error) {
247
+ if (error instanceof ParentContentNotFoundError) {
248
+ return reply.code(404).send({ message: error.message });
249
+ }
250
+ if (error instanceof ContentValidationError) {
251
+ return reply.code(400).send({ message: error.message, fieldErrors: error.failures });
252
+ }
253
+ if (error instanceof DocumentTypeNotFoundForContentError || error instanceof TreeRulesViolationError) {
254
+ // Same 400 slot as the field-validation branch above, so it must
255
+ // satisfy ContentValidationErrorSchema's required fieldErrors —
256
+ // there are no per-field failures for these two error kinds, so
257
+ // an empty array is the honest shape, not a placeholder.
258
+ return reply.code(400).send({ message: error.message, fieldErrors: [] });
259
+ }
260
+ throw error;
261
+ }
262
+ });
263
+ // Shared by PUT/PATCH: resolve the node (any publish state — writers edit
264
+ // drafts), compute the data to save per the verb, then go through
265
+ // saveContent (Session 106 field-validates the result). Returns the reply.
266
+ async function saveContentWrite(reply, id, computeData) {
267
+ const node = await adapter.getContentNode(id);
268
+ if (!node) {
269
+ return reply.code(404).send({ message: `No content found for id ${id}` });
270
+ }
271
+ const latest = await adapter.getLatestContentVersion(id);
272
+ if (!latest) {
273
+ return reply.code(404).send({ message: `No content found for id ${id}` });
274
+ }
275
+ try {
276
+ const version = await contentService.saveContent(id, computeData(latest), apiContentAuthorId);
277
+ // saveContent doesn't return the node; the node's path/parentId/locale
278
+ // are unchanged by a save, so reuse the one already fetched.
279
+ return reply.code(200).send(toWriteResponse(node, version));
280
+ }
281
+ catch (error) {
282
+ if (error instanceof ContentValidationError) {
283
+ return reply.code(400).send({ message: error.message, fieldErrors: error.failures });
284
+ }
285
+ throw error;
286
+ }
287
+ }
288
+ app.put("/api/v1/content/:id", {
289
+ onRequest: [requireApiKey, requireApiKeyScope("write")],
290
+ schema: {
291
+ description: "Full-replace update: the node's data becomes exactly the request body's `data`, as a new immutable version. The whole record is field-validated, so omitting a required field is a 400. Use PATCH to change a subset of fields.",
292
+ params: PublicContentParamsSchema,
293
+ body: PublicUpdateContentRequestSchema,
294
+ response: {
295
+ 200: PublicContentWriteResponseSchema,
296
+ 400: z.union([ContentValidationErrorSchema, ApiErrorSchema]),
297
+ 401: ApiErrorSchema,
298
+ 403: ApiErrorSchema,
299
+ 404: ApiErrorSchema,
300
+ },
301
+ },
302
+ }, async (request, reply) => saveContentWrite(reply, request.params.id, () => request.body.data));
303
+ app.patch("/api/v1/content/:id", {
304
+ onRequest: [requireApiKey, requireApiKeyScope("write")],
305
+ schema: {
306
+ description: "Partial update: the request body's `data` keys are shallow-merged over the node's latest version (top-level per-key replace — a JSON/object field value is replaced whole, never deep-merged), saved as a new immutable version. The merged result is field-validated. Omitting a required field the node already has succeeds.",
307
+ params: PublicContentParamsSchema,
308
+ body: PublicUpdateContentRequestSchema,
309
+ response: {
310
+ 200: PublicContentWriteResponseSchema,
311
+ 400: z.union([ContentValidationErrorSchema, ApiErrorSchema]),
312
+ 401: ApiErrorSchema,
313
+ 403: ApiErrorSchema,
314
+ 404: ApiErrorSchema,
315
+ },
316
+ },
317
+ }, async (request, reply) => saveContentWrite(reply, request.params.id, (latest) => ({ ...latest.data, ...request.body.data })));
318
+ app.post("/api/v1/content/:id/publish", {
319
+ onRequest: [requireApiKey, requireApiKeyScope("write")],
320
+ schema: {
321
+ description: "Publish the node's latest saved version, repointing current_version (no new version row). Makes the node readable on the published-only read surface.",
322
+ params: PublicContentParamsSchema,
323
+ response: {
324
+ 200: PublicContentWriteResponseSchema,
325
+ 401: ApiErrorSchema,
326
+ 403: ApiErrorSchema,
327
+ 404: ApiErrorSchema,
328
+ 500: ApiErrorSchema,
329
+ },
330
+ },
331
+ }, async (request, reply) => {
332
+ try {
333
+ const node = await contentService.publishContent(request.params.id);
334
+ const version = await adapter.getContentVersion(node.id, node.currentVersion);
335
+ if (!version) {
336
+ return reply.code(500).send({
337
+ message: `current_version (${node.currentVersion}) for ${node.id} has no matching content version`,
338
+ });
339
+ }
340
+ return reply.code(200).send(toWriteResponse(node, version));
341
+ }
342
+ catch (error) {
343
+ if (isNotFoundError(error)) {
344
+ return reply.code(404).send({ message: NOT_FOUND_MESSAGE });
345
+ }
346
+ throw error;
347
+ }
348
+ });
349
+ app.get("/api/v1/content/:id/children", {
350
+ onRequest: requireApiKey,
351
+ schema: {
352
+ params: PublicContentParamsSchema,
353
+ querystring: PublicContentListQuerySchema,
354
+ response: {
355
+ 200: PublicContentListResponseSchema,
356
+ 401: ApiErrorSchema,
357
+ 404: ApiErrorSchema,
358
+ },
359
+ },
360
+ }, async (request, reply) => {
361
+ const parent = await adapter.getContentNode(request.params.id);
362
+ if (!parent || parent.publishedAt === null) {
363
+ return reply.code(404).send({ message: NOT_FOUND_MESSAGE });
364
+ }
365
+ const { cursor, limit } = request.query;
366
+ const items = await adapter.getChildren(parent.id, { cursor, limit, publishedOnly: true });
367
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
368
+ return { items: toSummaries(items), nextCursor };
369
+ });
370
+ app.get("/api/v1/content/:id", {
371
+ onRequest: requireApiKey,
372
+ schema: {
373
+ params: PublicContentParamsSchema,
374
+ querystring: PublicContentItemQuerySchema,
375
+ response: {
376
+ 200: z.union([PublicContentItemSchema, PublicContentFormatResponseSchema]),
377
+ 400: ApiErrorSchema,
378
+ 401: ApiErrorSchema,
379
+ 404: ApiErrorSchema,
380
+ 500: ApiErrorSchema,
381
+ },
382
+ },
383
+ }, async (request, reply) => {
384
+ // Verified ONCE, up front — see the by-path route's identical
385
+ // comment. `getContentNode` never filters by publish state, so
386
+ // unlike by-path this anchor fetch was never the bug; the
387
+ // verify-once shape is kept identical across both routes anyway.
388
+ const previewContentId = request.query.previewToken
389
+ ? (verifyPreviewToken(request.query.previewToken)?.contentId ?? null)
390
+ : null;
391
+ const anchor = await adapter.getContentNode(request.params.id);
392
+ return sendPublishedItem(request, reply, anchor, request.query, previewContentId);
393
+ });
394
+ app.get("/api/v1/media", {
395
+ onRequest: requireApiKey,
396
+ schema: {
397
+ querystring: PublicMediaListQuerySchema,
398
+ response: { 200: PublicMediaListResponseSchema, 401: ApiErrorSchema },
399
+ },
400
+ }, async (request) => {
401
+ const { cursor, limit, search } = request.query;
402
+ const files = await mediaService.list({ cursor, limit, search });
403
+ const items = await Promise.all(files.map((file) => toPublicMediaFile(mediaService, file)));
404
+ const nextCursor = files.length === limit ? (files[files.length - 1]?.id ?? null) : null;
405
+ return { items, nextCursor };
406
+ });
407
+ app.get("/api/v1/media/:id", {
408
+ onRequest: requireApiKey,
409
+ schema: {
410
+ params: PublicContentParamsSchema,
411
+ response: { 200: PublicMediaFileSchema, 401: ApiErrorSchema, 404: ApiErrorSchema },
412
+ },
413
+ }, async (request, reply) => {
414
+ const media = await mediaService.getById(request.params.id);
415
+ if (!media) {
416
+ return reply.code(404).send({ message: `No media file found for id ${request.params.id}` });
417
+ }
418
+ return toPublicMediaFile(mediaService, media);
419
+ });
420
+ // Public document type introspection + template listing (Session 105).
421
+ // Both are read-only projections over existing backoffice adapter calls
422
+ // (document-types.ts / templates.ts) — no new adapter methods.
423
+ app.get("/api/v1/document-types", {
424
+ onRequest: requireApiKey,
425
+ schema: {
426
+ querystring: PublicDocumentTypeListQuerySchema,
427
+ response: { 200: PublicDocumentTypeListResponseSchema, 401: ApiErrorSchema },
428
+ },
429
+ }, async (request) => {
430
+ const { cursor, limit } = request.query;
431
+ const items = await adapter.listDocumentTypes({ cursor, limit });
432
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
433
+ return { items: items.map(toPublicDocumentType), nextCursor };
434
+ });
435
+ app.get("/api/v1/document-types/:id", {
436
+ onRequest: requireApiKey,
437
+ schema: {
438
+ params: PublicContentParamsSchema,
439
+ response: { 200: PublicDocumentTypeSchema, 401: ApiErrorSchema, 404: ApiErrorSchema },
440
+ },
441
+ }, async (request, reply) => {
442
+ const doc = await adapter.getDocumentType(request.params.id);
443
+ if (!doc) {
444
+ return reply.code(404).send({ message: `No document type found for id ${request.params.id}` });
445
+ }
446
+ return toPublicDocumentType(doc);
447
+ });
448
+ app.get("/api/v1/templates", {
449
+ onRequest: requireApiKey,
450
+ schema: {
451
+ querystring: PublicTemplateListQuerySchema,
452
+ response: { 200: PublicTemplateListResponseSchema, 401: ApiErrorSchema },
453
+ },
454
+ }, async (request) => {
455
+ const { cursor, limit, documentTypeId } = request.query;
456
+ const items = await adapter.listTemplates({ cursor, limit, documentTypeId });
457
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
458
+ return { items, nextCursor };
459
+ });
460
+ // GraphQL surface (Session 113, Task 2) — same public-content namespace,
461
+ // a second protocol over the identical published-item pipeline as the
462
+ // REST routes above. Nested scope so the key-gate hook below only
463
+ // touches this plugin's own routes, never the REST ones (which already
464
+ // gate per-route via onRequest: requireApiKey).
465
+ await app.register(async (gql) => {
466
+ // Key-gate ONLY the data endpoint: GraphiQL's HTML/asset routes ride
467
+ // the docs flag alone (its queries still need a key like any caller,
468
+ // same as swagger-ui showing schema shape without exposing data).
469
+ // mercurius' graphiql page lives at /api/v1/graphiql (and
470
+ // /api/v1/graphiql/*), which never matches this startsWith check.
471
+ gql.addHook("onRequest", async (request, reply) => {
472
+ if (request.url.startsWith("/api/v1/graphql")) {
473
+ await requireApiKey(request, reply);
474
+ }
475
+ });
476
+ // Dual-package hazard: this file is loaded as ESM, so `import {
477
+ // NoSchemaIntrospectionCustomRule } from "graphql"` resolves the ESM
478
+ // build — a DIFFERENT module instance than the CJS `graphql` mercurius
479
+ // (itself CJS) requires internally to validate queries. A rule object
480
+ // built from the ESM instance fails `instanceof`/identity checks
481
+ // inside mercurius' CJS graphql-js, so instead of a clean
482
+ // introspection-disabled rejection every query 500s with "Cannot use
483
+ // GraphQLNonNull ... from another module or realm ... Duplicate
484
+ // "graphql" modules". createRequire forces Node's CJS resolver, which
485
+ // hits the very same `graphql/index.js` mercurius itself requires —
486
+ // one instance, no realm mismatch.
487
+ const cjsGraphql = createRequire(import.meta.url)("graphql");
488
+ const { GraphQLError, NoSchemaIntrospectionCustomRule } = cjsGraphql;
489
+ // field-count-rule.ts's rule reports via `context.reportError(new
490
+ // GraphQLErrorCtor(...))` — the GraphQLError class it's given here is
491
+ // the SAME createRequire'd one NoSchemaIntrospectionCustomRule above
492
+ // is already built from, so every error either rule reports lives in
493
+ // the one true realm mercurius' CJS graphql-js instance validates
494
+ // against (see field-count-rule.ts's realm note for the full
495
+ // reasoning; S113 is the original incident this guards against).
496
+ const fieldCountRule = createFieldCountRule(maxQueryComplexity(), GraphQLError);
497
+ // mercurius registers its GraphiQL page (/api/v1/graphiql,
498
+ // .../main.js, .../sw.js, .../config.js) as plain, schema-less Fastify
499
+ // routes. Without an explicit `hide`, fastify-swagger picks each one
500
+ // up as a bare, undocumented path entry — and because this plugin
501
+ // scope is nested under BOTH the internal (/api/docs) and public
502
+ // (/api/v1/docs) Swagger surfaces, they leak into both specs. Hide
503
+ // them before mercurius registers those routes.
504
+ gql.addHook("onRoute", (routeOptions) => {
505
+ if (routeOptions.url.startsWith("/api/v1/graphiql")) {
506
+ routeOptions.schema = { ...routeOptions.schema, hide: true };
507
+ }
508
+ });
509
+ await gql.register(mercurius, {
510
+ schema: buildGraphqlSchema(),
511
+ prefix: "/api/v1",
512
+ path: "/graphql",
513
+ // mercurius 16's graphiql option has no per-path override — its
514
+ // GraphiQL page is always served at the plugin's own `/graphiql`,
515
+ // so `prefix` above is what nests it under /api/v1 alongside the
516
+ // data endpoint. Read at registration (boot) time, same convention
517
+ // as the /api/v1/docs Swagger UI gate in app.ts.
518
+ graphiql: isPublicDocsEnabled() ? { enabled: true } : false,
519
+ queryDepth: maxQueryDepth(),
520
+ // Session 119, Task 2: array-form validationRules (unlike the S115
521
+ // function-form workaround graphql-query-complexity required) keeps
522
+ // mercurius' query-plan cache on — the S118 baseline measured the
523
+ // function-form/`cache: false` alternative at ~10ms higher p50 and
524
+ // 23% lower throughput on GraphQL item reads.
525
+ validationRules: [
526
+ ...(isPublicDocsEnabled() ? [] : [NoSchemaIntrospectionCustomRule]),
527
+ fieldCountRule,
528
+ ],
529
+ additionalRouteOptions: {
530
+ validatorCompiler: mercuriusValidatorCompiler,
531
+ serializerCompiler: mercuriusSerializerCompiler,
532
+ },
533
+ context: async (request) => ({
534
+ deps: {
535
+ adapter,
536
+ listSerializerContributions: () => app.pluginRegistry.listActiveSerializerContributions(),
537
+ warn: (obj, msg) => request.log.warn(obj, msg),
538
+ },
539
+ contentService,
540
+ mediaService,
541
+ getContentNode: (id) => adapter.getContentNode(id),
542
+ }),
543
+ });
544
+ });
545
+ };
@@ -0,0 +1,57 @@
1
+ import type { DatabaseAdapter } from "@velora-cms/db-adapter";
2
+ import { type ContentService } from "@velora-cms/content-service";
3
+ import type { ContentNode, DocumentTypeDefinition, MediaFile, PublicContentNodeSummary, PublicDocumentType, PublicMediaFile } from "@velora-cms/api-schemas";
4
+ import type { SerializerContext, SerializerContribution } from "@velora-cms/plugin-sdk/manifest";
5
+ import type { MediaService } from "../media/media-service.js";
6
+ export declare function isNotFoundError(error: unknown): error is Error;
7
+ export declare function resolveItemNode(deps: {
8
+ adapter: DatabaseAdapter;
9
+ contentService: ContentService;
10
+ }, anchor: ContentNode | null, query: {
11
+ locale?: string;
12
+ fallback?: string;
13
+ }, options?: {
14
+ previewContentId?: string | null;
15
+ }): Promise<ContentNode | null>;
16
+ export declare function resolveSubtreeFilter(adapter: DatabaseAdapter, underId: string): Promise<{
17
+ pathPrefix: string;
18
+ } | null>;
19
+ export declare function toSummaries(nodes: ContentNode[]): PublicContentNodeSummary[];
20
+ export declare function toPublicDocumentType(doc: DocumentTypeDefinition): PublicDocumentType;
21
+ export declare function toPublicMediaFile(mediaService: MediaService, media: MediaFile): Promise<PublicMediaFile>;
22
+ export type PublishedItemDeps = {
23
+ adapter: DatabaseAdapter;
24
+ listSerializerContributions: () => Array<{
25
+ pluginId: string;
26
+ serializers: SerializerContribution;
27
+ }>;
28
+ warn: (obj: Record<string, unknown>, msg: string) => void;
29
+ };
30
+ export type PreparedPublishedItem = {
31
+ status: "notFound";
32
+ } | {
33
+ status: "brokenVersion";
34
+ message: string;
35
+ } | {
36
+ status: "ok";
37
+ core: {
38
+ id: string;
39
+ documentTypeId: string;
40
+ locale: string;
41
+ publishedAt: string | null;
42
+ version: number;
43
+ };
44
+ data: Record<string, unknown>;
45
+ contributions: Array<{
46
+ pluginId: string;
47
+ serializers: SerializerContribution;
48
+ }>;
49
+ buildSerializerContext: () => SerializerContext;
50
+ };
51
+ export type PreparePublishedItemOptions = {
52
+ previewContentId?: string | null;
53
+ };
54
+ export declare function preparePublishedItem(deps: PublishedItemDeps, anchor: ContentNode | null, options?: PreparePublishedItemOptions): Promise<PreparedPublishedItem>;
55
+ export declare function computeExtensions(prepared: Extract<PreparedPublishedItem, {
56
+ status: "ok";
57
+ }>, warn: PublishedItemDeps["warn"]): Promise<Record<string, Record<string, unknown>> | undefined>;