@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,477 @@
1
+ import { ApiErrorSchema, SetupCompleteRequestSchema, SetupCompleteResponseSchema, SetupStatusResponseSchema } from "@velora-cms/api-schemas";
2
+ import { clientDirFor } from "../scripts/generate-prisma-clients.js";
3
+ import { loadPrismaClient } from "../db/load-prisma-client.js";
4
+ import { buildConnectionString } from "../setup/build-connection-string.js";
5
+ import { provisionDatabase } from "../setup/provision-database.js";
6
+ import { persistDatabaseConfig } from "../setup/persist-env-config.js";
7
+ import { hashPassword } from "../auth/tokens.js";
8
+ import { listBundledTemplates } from "../setup/bundled-templates.js";
9
+ import { installStarterTemplate } from "../setup/install-starter.js";
10
+ import { SETUP_RATE_LIMIT, ipOnlyRateLimitConfig } from "../auth/route-rate-limits.js";
11
+ import { resolveEnvFilePath } from "../config/env-file-path.js";
12
+ import { dialectFromUrlScheme } from "../db/content-db.js";
13
+ import { applyCoreMigrations } from "../db/core-migrations/index.js";
14
+ import { ensurePrismaClientGenerated } from "../setup/ensure-prisma-client.js";
15
+ import { ADMIN_PROBE_TIMEOUT_MS, countAdminUsers, withTimeout } from "../boot/resolve-boot-mode.js";
16
+ // F6 (fix wave 1)/M3: named so the 409/503 message literals exist exactly
17
+ // once each, and tests assert against these constants instead of
18
+ // duplicating the string or matching a regex against it.
19
+ export const SETUP_ALREADY_COMPLETED_MESSAGE = "Setup has already been completed.";
20
+ export const SETUP_PROBE_FAILED_MESSAGE = "Could not verify whether setup has already been completed. Try again.";
21
+ // M2: the two pairing guards below constrain `database` (the request
22
+ // body), not DATABASE_URL (the environment) — they cannot, by themselves,
23
+ // prove process.env.DATABASE_URL is actually set. In production
24
+ // opts.databaseConfigured: true is passed by index.ts after
25
+ // resolveBootMode() (boot/resolve-boot-mode.ts) confirmed DATABASE_URL is
26
+ // set (the setup-admin-only path), and also by app.ts's own registration
27
+ // of this same route with { setupRequired: false, databaseConfigured:
28
+ // true } (the already-fully-configured, buildApp() path — DATABASE_URL is
29
+ // always set there too, or buildApp() itself would already have failed
30
+ // elsewhere). Harmless in both real callers because the 409
31
+ // already-completed guard fires first on that second path, but
32
+ // buildSetupOnlyApp() is exported and callable with
33
+ // { databaseConfigured: true } independently of either guarantee (tests
34
+ // do exactly this) — so this message exists for the case where neither
35
+ // production invariant holds.
36
+ export const SETUP_MISCONFIGURED_DATABASE_URL_MESSAGE = "Setup is misconfigured: the database is reported as configured, but no database URL is set.";
37
+ // Task 5 final follow-ups (S164 review), item 1: distinct from
38
+ // SETUP_ALREADY_COMPLETED_MESSAGE on purpose. A setup_lock row and its
39
+ // admin commit together (createFirstAdmin's own transaction, below), so a
40
+ // setup_lock row surviving with zero admins can only mean an admin was
41
+ // deleted after setup completed — the classic lost-password recovery.
42
+ // Without this distinct message that state answered the ordinary "already
43
+ // completed" 409 forever, with nothing naming setup_lock as the cause and
44
+ // no way back in. See createFirstAdmin's SetupLockWedgedError.
45
+ export const SETUP_LOCK_WEDGED_MESSAGE = "Setup cannot run: a setup_lock row exists but no admin account does. This only happens when an " +
46
+ "administrator account was deleted after setup had already completed. To recover, delete the single " +
47
+ "row (id = 1) from the setup_lock table, then re-run setup.";
48
+ /** Duck-typed on `.code`, same reasoning as
49
+ * marketplace/install-from-marketplace.ts's own isUniqueConstraintViolation:
50
+ * the client loaded here comes from a per-dialect generated module
51
+ * (db/load-prisma-client.ts), a different module instance than any
52
+ * statically imported `@prisma/client`, so `instanceof
53
+ * Prisma.PrismaClientKnownRequestError` would always be false across that
54
+ * boundary. `.code` is stable regardless of which client instance threw. */
55
+ function isUniqueConstraintViolation(error) {
56
+ return typeof error === "object" && error !== null && "code" in error && error.code === "P2002";
57
+ }
58
+ // Task 5 final follow-ups (S164 review), item 1: thrown by createFirstAdmin
59
+ // when it can prove the setup_lock sentinel is wedged (row present, zero
60
+ // admins) rather than just an ordinary concurrent loser. Caught at the
61
+ // route call site below and mapped to SETUP_LOCK_WEDGED_MESSAGE instead of
62
+ // SETUP_ALREADY_COMPLETED_MESSAGE. Not exported: nothing outside this
63
+ // module needs to name it, only catch it.
64
+ class SetupLockWedgedError extends Error {
65
+ }
66
+ // F3 (fix wave 2, S164 review): creates the first admin as one atomic
67
+ // unit, GUARANTEED atomic on every Tier 1 dialect — not just narrowed —
68
+ // unlike fix wave 1's count-and-create-in-one-$transaction, which this
69
+ // replaces.
70
+ //
71
+ // Fix wave 1's claim was wrong: a Prisma interactive transaction inherits
72
+ // the database's default isolation (READ COMMITTED on Postgres,
73
+ // REPEATABLE READ on MySQL), and a plain COUNT is a non-locking snapshot
74
+ // read with no phantom protection for a later INSERT in the same
75
+ // transaction. Reproduced live — two genuinely concurrent
76
+ // createFirstAdmin-shaped transactions, each on its own connection,
77
+ // BOTH committed a create on both postgresql and mysql, at gaps from 5ms
78
+ // to 200ms. Only SQLite hid this, because Prisma's single-connection pool
79
+ // serializes every write there regardless of isolation level — the one
80
+ // dialect fix wave 1's own test exercised. That test was a sequential
81
+ // stale-read scenario (any isolation level handles that); it was never
82
+ // evidence of atomicity under real concurrency.
83
+ //
84
+ // The fix: SetupLock (prisma/schema.prisma) is a fixed-PK singleton row.
85
+ // Every completion — admin-only AND setup-full, closing M-3 as well as F3
86
+ // since neither branches on mode anymore — inserts it unconditionally,
87
+ // inside the same transaction as the admin count/create. A PK insert is
88
+ // enforced by the storage engine itself: a second concurrent INSERT of
89
+ // the same key value blocks until the first inserting transaction
90
+ // resolves, then fails with a real unique-constraint violation (P2002) if
91
+ // it committed — on every Tier 1 dialect, regardless of isolation level.
92
+ // That is a fundamentally different guarantee than a COUNT snapshot read,
93
+ // which is why this closes the race a plain count-and-create could not.
94
+ //
95
+ // Task 5 final follow-ups (S164 review), item 3: this only holds for as
96
+ // long as the WINNER's transaction stays inside Prisma's default 5-second
97
+ // interactive-transaction timeout. Past that window the loser's blocked
98
+ // INSERT sees the winner's transaction time out and roll back rather than
99
+ // commit, so the loser's own statement resolves with no conflict at all —
100
+ // or, if the loser's OWN transaction is what times out while still
101
+ // blocked, Prisma raises P2028 instead of P2002. Neither is caught by
102
+ // isUniqueConstraintViolation below, so it is rethrown out of this
103
+ // function and — uncaught by the route handler beyond that — becomes an
104
+ // undeclared 500 rather than this function's 409. Still fail-closed (no
105
+ // second admin either way, since one of the two writes always loses), just
106
+ // a different status code once a transaction runs that long.
107
+ async function createFirstAdmin(client, admin) {
108
+ const data = {
109
+ email: admin.email,
110
+ name: admin.name,
111
+ passwordHash: await hashPassword(admin.password),
112
+ role: "admin",
113
+ };
114
+ try {
115
+ return await client.$transaction(async (tx) => {
116
+ const existingAdmins = await tx.user.count({ where: { role: "admin" } });
117
+ if (existingAdmins > 0)
118
+ return null;
119
+ // The loser of a genuine concurrent race throws P2002 here, caught
120
+ // below — this is what actually closes the window; the count above
121
+ // is only what makes an ALREADY-admin'd database (a pre-existing
122
+ // admin-only install predating this fix, which never got a
123
+ // SetupLock row) refuse without needing the sentinel at all.
124
+ await tx.setupLock.create({ data: { id: 1 } });
125
+ return tx.user.create({ data });
126
+ });
127
+ }
128
+ catch (err) {
129
+ if (isUniqueConstraintViolation(err)) {
130
+ // Task 5 final follow-ups (S164 review), item 1: the transaction
131
+ // above just aborted on the storage engine's own P2002 — on Postgres
132
+ // in particular, an aborted transaction refuses every further
133
+ // statement until it rolls back, so there is no way to keep reading
134
+ // INSIDE it. This recount is therefore its own separate read against
135
+ // `client`, taken after that rollback has already happened.
136
+ //
137
+ // A count of zero here is provable evidence of the wedge: the
138
+ // sentinel and its admin always commit together (same transaction,
139
+ // above), so a P2002 on the sentinel with STILL zero admins means
140
+ // the row that P2002 collided with cannot have been created by a
141
+ // request racing this one — it can only be a sentinel left behind by
142
+ // an admin that existed and was later deleted. A non-zero count
143
+ // means an admin exists right now — either a genuine concurrent
144
+ // winner, or a pre-existing admin — the ordinary case.
145
+ //
146
+ // This has its own (narrower) race: a genuine concurrent winner
147
+ // could still be between its P2002-causing commit and this recount,
148
+ // in which case this recount could read 0 and misreport an ordinary
149
+ // race as a wedge. It cannot misreport in the other direction — an
150
+ // ordinary winner's commit is exactly what would make this count
151
+ // non-zero — so the mistake this leaves possible is a false wedge
152
+ // report, never a false "all clear," and either outcome still
153
+ // refuses with a 409 and creates no second admin.
154
+ const adminsNow = await client.user.count({ where: { role: "admin" } });
155
+ if (adminsNow === 0)
156
+ throw new SetupLockWedgedError();
157
+ return null;
158
+ }
159
+ throw err;
160
+ }
161
+ }
162
+ // Registered by BOTH apps: buildApp() with setupRequired: false (a
163
+ // fully-configured instance answering "no, setup already happened"),
164
+ // buildSetupOnlyApp() with setupRequired: true — one shared route
165
+ // definition instead of two near-identical copies. Session 164 adds a
166
+ // third caller shape: buildSetupOnlyApp({ databaseConfigured: true }),
167
+ // the admin-only mode — still setupRequired: true (the wizard is still
168
+ // running), but with a database already injected and read-only.
169
+ export const setupRoutes = async (app, opts) => {
170
+ // F2 (fix wave 1): closes the setup-completion window for as long as
171
+ // THIS registered app instance keeps running, without waiting for a
172
+ // restart. In production index.ts builds exactly one app per boot
173
+ // (buildApp() or buildSetupOnlyApp(), never both), so this closure's
174
+ // lifetime IS the process's lifetime — "in-process latch." Scoped to
175
+ // this plugin registration rather than module scope so every
176
+ // independently built app (every test that calls buildSetupOnlyApp()/
177
+ // buildTestApp() gets its own fresh app) starts with its own latch too;
178
+ // a module-level flag would leak between unrelated app instances built
179
+ // in the same test process. Set the moment a completion actually
180
+ // succeeds, below, and checked at entry here — before the setupRequired
181
+ // check reads the boot-time flag opts.setupRequired never changes for
182
+ // the lifetime of this closure, which is exactly why a request-time
183
+ // re-check was needed at all (S164 final review of Task 4): without it,
184
+ // a first legitimate completion would not close this endpoint on its
185
+ // own — it would keep accepting unauthenticated admin-creation/database-
186
+ // provisioning requests until the next restart.
187
+ let setupCompleted = false;
188
+ // S162: an unauthenticated POST that provisions a database and creates
189
+ // the first admin. It 409s on an already-configured instance (S151), so
190
+ // the ceiling is about making the pre-configuration window uninteresting
191
+ // to probe, not about the configured steady state.
192
+ //
193
+ // ipOnlyRateLimitConfig() (I6, S162 final review; reshaped into a builder
194
+ // by Session 163's gate run), not the shared three-lane rateLimitConfig: this
195
+ // scope is also registered inside buildSetupOnlyApp() (setup-app.ts), the
196
+ // not-yet-configured boot path documented as never touching Prisma.
197
+ // rateLimitConfig imports auth/api-keys.ts -> db/prisma.ts, which has a
198
+ // top-level `await loadPrismaClient(...)` — importing it here would make
199
+ // that documented invariant false. Setup has no API keys or JWT sessions
200
+ // to lane anyway, so the plain IP-keyed config costs nothing behaviorally.
201
+ //
202
+ // Called here, not passed as a bare reference: it's a builder function now
203
+ // (route-rate-limits.ts), not a plain object, so the plugin receives the
204
+ // built options object — evaluated at this exact registration point, same
205
+ // read-timing as before the reshape.
206
+ await app.register(import("@fastify/rate-limit"), ipOnlyRateLimitConfig());
207
+ app.get("/api/setup/status", {
208
+ schema: {
209
+ response: {
210
+ 200: SetupStatusResponseSchema,
211
+ },
212
+ },
213
+ }, async () => ({
214
+ setupRequired: opts.setupRequired,
215
+ // Only offered while setup is actually in progress — a
216
+ // fully-configured instance (setupRequired: false) has nothing left
217
+ // to pick a starter template for, so this route never bothers
218
+ // resolving the bundle on that path.
219
+ bundledTemplates: opts.setupRequired ? listBundledTemplates() : [],
220
+ databaseConfigured: opts.databaseConfigured,
221
+ }));
222
+ app.post("/api/setup/complete", {
223
+ schema: {
224
+ body: SetupCompleteRequestSchema,
225
+ response: {
226
+ 200: SetupCompleteResponseSchema,
227
+ 400: ApiErrorSchema,
228
+ 409: ApiErrorSchema,
229
+ 500: ApiErrorSchema,
230
+ 503: ApiErrorSchema,
231
+ },
232
+ },
233
+ config: { rateLimit: { max: SETUP_RATE_LIMIT.max(), timeWindow: SETUP_RATE_LIMIT.timeWindow() } },
234
+ }, async (request, reply) => {
235
+ // buildApp() registers this route with setupRequired: false — on a
236
+ // configured instance, completing setup again would let any
237
+ // unauthenticated caller provision an attacker-supplied database,
238
+ // create their own admin in it, and REWRITE the real .env. Refuse
239
+ // before any provisioning side effect. setupCompleted (F2 above)
240
+ // answers the identical 409 once THIS app instance has already
241
+ // completed setup once, even though opts.setupRequired itself never
242
+ // changes.
243
+ if (!opts.setupRequired || setupCompleted) {
244
+ return reply.code(409).send({ message: SETUP_ALREADY_COMPLETED_MESSAGE });
245
+ }
246
+ const { admin, database, starter } = request.body;
247
+ // The pairing is enforced here, not by the schema, and in BOTH
248
+ // directions. Accepting-and-ignoring a selection would let an
249
+ // operator believe they had changed the database when the injected
250
+ // DATABASE_URL is what actually stands.
251
+ if (opts.databaseConfigured && database) {
252
+ return reply.code(400).send({
253
+ message: "The database is already configured by the environment and cannot be changed here. " +
254
+ "Remove the database selection from this request.",
255
+ });
256
+ }
257
+ if (!opts.databaseConfigured && !database) {
258
+ return reply.code(400).send({ message: "A database selection is required to complete setup." });
259
+ }
260
+ // Security fix (S164 final review of Task 4): opts.setupRequired /
261
+ // opts.databaseConfigured are frozen at BOOT — read once by
262
+ // index.ts's call into resolveBootMode() (boot/resolve-boot-mode.ts)
263
+ // and handed to whichever app got built. In admin-only mode this
264
+ // route stays registered, and therefore reachable, for as long as
265
+ // the process keeps running. Without this re-check, a first
266
+ // legitimate call that creates the admin would not close this
267
+ // endpoint: it would stay an unauthenticated "provision + create
268
+ // admin + rewrite DATABASE_URL" route until someone restarts the
269
+ // process. Re-probing here, at request time, closes that window
270
+ // immediately instead of on the next boot. Reuses countAdminUsers
271
+ // (boot/resolve-boot-mode.ts) — the same admin-existence question
272
+ // resolveBootMode already answers at boot, not a second
273
+ // implementation of it. F4 (fix wave 1): bounded with the same
274
+ // withTimeout/ADMIN_PROBE_TIMEOUT_MS resolveBootMode uses for its
275
+ // own boot-time probe — an unbounded await here would hang the
276
+ // REQUEST forever on a wedged database (M-1, fix wave 2: withTimeout
277
+ // does not cancel the underlying countAdminUsers() promise or
278
+ // disconnect its client early — it only races a timer against it, so
279
+ // the client is held open until that probe eventually settles on its
280
+ // own. What this bound actually buys is that the CALLER stops
281
+ // waiting and gets a named 503 instead of a hang; it is not a
282
+ // resource-leak fix).
283
+ //
284
+ // This only applies in admin-only mode (opts.databaseConfigured &&
285
+ // opts.setupRequired — setup-full has no configured database to
286
+ // probe, and setupRequired: false already returned 409 above). This
287
+ // pre-check is a cheap, non-atomic optimization — it lets an
288
+ // already-configured instance reject FAST, before running `prisma db
289
+ // push` or opening a real connection for the create below, but it
290
+ // does not by itself close the race between two concurrent
291
+ // requests. createFirstAdmin's own sentinel-backed transaction
292
+ // (F3, fix wave 2, above) is what actually closes that race; this
293
+ // check just keeps the common case cheap.
294
+ if (opts.databaseConfigured) {
295
+ let admins;
296
+ try {
297
+ admins = await withTimeout(countAdminUsers(), ADMIN_PROBE_TIMEOUT_MS);
298
+ }
299
+ catch (err) {
300
+ // FAIL CLOSED, matching resolveBootMode's own doctrine
301
+ // (boot/resolve-boot-mode.ts): a probe that rejects must never
302
+ // be read as "no admin yet" — that misreading is exactly the
303
+ // hole Task 4's review caught one level up, at boot. 503, not
304
+ // 400 or 500: this is neither a bad request nor a generic bug,
305
+ // it is the server unable to confirm whether setup is still
306
+ // open, and the operator should retry once the database answers.
307
+ app.log.error({ err }, "setup/complete: admin re-probe failed");
308
+ return reply.code(503).send({ message: SETUP_PROBE_FAILED_MESSAGE });
309
+ }
310
+ // Same "resolved but not a real count" doctrine as
311
+ // resolveBootMode: NaN, a negative number, or anything else that
312
+ // isn't a non-negative integer must refuse, not be treated as
313
+ // zero.
314
+ if (!Number.isInteger(admins) || admins < 0) {
315
+ app.log.error({ admins }, "setup/complete: admin re-probe resolved with a non-count value");
316
+ return reply.code(503).send({ message: SETUP_PROBE_FAILED_MESSAGE });
317
+ }
318
+ if (admins > 0) {
319
+ return reply.code(409).send({ message: SETUP_ALREADY_COMPLETED_MESSAGE });
320
+ }
321
+ }
322
+ // In admin-only mode the URL comes from the environment, already
323
+ // normalised in index.ts. M2 (fix wave 1): the two pairing guards
324
+ // above make exactly one of these branches reachable IN PRODUCTION —
325
+ // `database` is defined whenever opts.databaseConfigured is false,
326
+ // and DATABASE_URL is guaranteed set whenever it's true, because
327
+ // that is what "databaseConfigured" means (see BootMode /
328
+ // resolveBootMode in boot/resolve-boot-mode.ts, and index.ts's call
329
+ // into buildSetupOnlyApp). But that guarantee lives in index.ts, not
330
+ // in this function's own inputs — buildSetupOnlyApp() is exported
331
+ // and callable with { databaseConfigured: true } independently of
332
+ // it (tests do exactly this), so a bare `!` here would trade a
333
+ // declared, tested 500 for an undeclared, untested one the moment
334
+ // that external guarantee doesn't hold. Checked explicitly instead.
335
+ let databaseUrl;
336
+ if (database) {
337
+ databaseUrl = buildConnectionString(database);
338
+ }
339
+ else if (process.env.DATABASE_URL) {
340
+ databaseUrl = process.env.DATABASE_URL;
341
+ }
342
+ else {
343
+ return reply.code(500).send({ message: SETUP_MISCONFIGURED_DATABASE_URL_MESSAGE });
344
+ }
345
+ const dialect = database?.dialect ?? dialectFromUrlScheme(databaseUrl);
346
+ if (!dialect) {
347
+ return reply.code(400).send({
348
+ message: `The configured DATABASE_URL does not name a supported database (${databaseUrl.split(":")[0]}:).`,
349
+ });
350
+ }
351
+ try {
352
+ // Runs on BOTH paths. `prisma db push` is idempotent, and an
353
+ // injected DATABASE_URL may point at a completely empty database —
354
+ // without this, the admin create below would fail on a missing
355
+ // table. S185: the Prisma client self-heal runs first — not
356
+ // because `db push --skip-generate` itself needs a generated
357
+ // client (it doesn't), but because the `loadPrismaClient(
358
+ // clientDirFor(dialect))` call a few lines below this block does —
359
+ // and the core-migration ledger is recorded in "fresh-push" mode
360
+ // right after — db push just created the CURRENT schema from
361
+ // scratch, so every step is already reflected and gets recorded
362
+ // without executing any SQL.
363
+ await ensurePrismaClientGenerated(dialect);
364
+ await provisionDatabase(dialect, databaseUrl);
365
+ await applyCoreMigrations({ dialect, databaseUrl, mode: "fresh-push" });
366
+ }
367
+ catch (err) {
368
+ return reply.code(400).send({ message: err instanceof Error ? err.message : "Failed to provision database" });
369
+ }
370
+ const client = await loadPrismaClient(clientDirFor(dialect), databaseUrl);
371
+ // The optional starter install (Session 151) needs both the created
372
+ // admin row's id (sample content's createdBy) and the still-connected
373
+ // client, so the disconnect moved from around the create alone to
374
+ // after the whole block. Without a starter this executes exactly the
375
+ // operations it always did — create, disconnect, persist — nothing
376
+ // content-db-shaped ever runs on the skip path.
377
+ let starterResult;
378
+ let adminUser = null;
379
+ // Task 5 final follow-ups (S164 review), item 1: set only when
380
+ // createFirstAdmin proved the setup_lock sentinel is wedged (see its
381
+ // own comment) — distinct from the ordinary `!adminUser` refusal
382
+ // below, which covers both "an admin already exists" and "an
383
+ // ordinary concurrent loser."
384
+ let wedged = false;
385
+ try {
386
+ // F3 (fix wave 2): unconditional now — see createFirstAdmin's own
387
+ // comment for why the sentinel-backed transaction applies to both
388
+ // modes, closing M-3 (setup-full vs. setup-full) as well as the
389
+ // original admin-only race.
390
+ try {
391
+ adminUser = await createFirstAdmin(client, admin);
392
+ }
393
+ catch (err) {
394
+ if (err instanceof SetupLockWedgedError) {
395
+ wedged = true;
396
+ }
397
+ else {
398
+ throw err;
399
+ }
400
+ }
401
+ // M-2 (fix wave 2): latched immediately after a real create
402
+ // succeeds — inside this try, before the starter install and
403
+ // before $disconnect() below. Fix wave 1 set this flag only after
404
+ // the whole try/finally returned, which left a window: if
405
+ // $disconnect() throws AFTER a successful create, the handler 500s
406
+ // with setupCompleted still false, reopening (for setup-full) the
407
+ // exact unauthenticated-completion window F2 exists to close. A
408
+ // starter failure below still can't unwind the admin account or
409
+ // the database config (installStarterTemplate never throws), so
410
+ // latching before it runs is safe.
411
+ if (adminUser) {
412
+ setupCompleted = true;
413
+ }
414
+ if (adminUser && starter) {
415
+ // Never throws — a starter failure is reported in the response,
416
+ // it must not unwind the admin account or the database config.
417
+ starterResult = await installStarterTemplate({
418
+ prisma: client,
419
+ databaseUrl,
420
+ adminUserId: adminUser.id,
421
+ starter,
422
+ });
423
+ }
424
+ }
425
+ finally {
426
+ await client.$disconnect();
427
+ }
428
+ if (wedged) {
429
+ // Task 5 final follow-ups (S164 review), item 1: named and logged
430
+ // so an operator locked out by a deleted admin has both a
431
+ // diagnosable response and a server-side record of when/why —
432
+ // unlike the silent, indistinguishable 409 this replaces.
433
+ app.log.error({ attemptedEmail: admin.email }, "setup/complete: setup_lock sentinel exists with zero admins — an admin was likely deleted after setup completed");
434
+ return reply.code(409).send({ message: SETUP_LOCK_WEDGED_MESSAGE });
435
+ }
436
+ if (!adminUser) {
437
+ // F3: the atomic transaction's own count/sentinel saw an admin
438
+ // that did not exist when the cheap pre-check ran above — another
439
+ // request, in this same process, won the race and created it
440
+ // first. Same 409 shape as every other "setup already completed"
441
+ // answer.
442
+ return reply.code(409).send({ message: SETUP_ALREADY_COMPLETED_MESSAGE });
443
+ }
444
+ // Only the setup-full path has anything to persist. In admin-only
445
+ // mode DATABASE_URL was already set at boot — by the environment or
446
+ // by a previous run's env file — so there is nothing new to write,
447
+ // and writing would risk contradicting what the next restart is
448
+ // injected with. See BootMode / resolveBootMode in
449
+ // boot/resolve-boot-mode.ts.
450
+ let configWarning;
451
+ if (!opts.databaseConfigured) {
452
+ try {
453
+ persistDatabaseConfig(databaseUrl);
454
+ }
455
+ catch (err) {
456
+ // Never unwind. The admin row and any starter install have
457
+ // already succeeded, and throwing here would lose them while
458
+ // leaving the database provisioned — the same reasoning the
459
+ // starter failure path uses a few lines above. Report it
460
+ // instead, naming the resolved path, so the operator can set
461
+ // DATABASE_URL themselves and restart.
462
+ configWarning =
463
+ `Setup finished, but the configuration could not be saved to ${resolveEnvFilePath()}: ` +
464
+ `${err instanceof Error ? err.message : String(err)}. ` +
465
+ "Set DATABASE_URL in the environment before restarting, or this instance will run setup again.";
466
+ }
467
+ }
468
+ // `starter` and `configWarning` stay ABSENT (not null) when there is
469
+ // nothing to report, keeping the ordinary response byte-identical to
470
+ // the pre-Session-164 shape.
471
+ return {
472
+ success: true,
473
+ ...(starterResult ? { starter: starterResult } : {}),
474
+ ...(configWarning ? { configWarning } : {}),
475
+ };
476
+ });
477
+ };
@@ -0,0 +1,3 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ import type { ContentDb } from "../db/content-db.js";
3
+ export declare const snapshotRoutes: FastifyPluginAsyncZod<ContentDb>;
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { SnapshotService } from "@velora-cms/content-service";
3
+ import { ApiErrorSchema, CreateSnapshotRequestSchema, RestoreSnapshotResponseSchema, SnapshotListQuerySchema, SnapshotListResponseSchema, SnapshotResponseSchema, } from "@velora-cms/api-schemas";
4
+ import { requireAdmin } from "../auth/require-auth.js";
5
+ const SnapshotParamsSchema = z.object({ id: z.string().uuid() });
6
+ // Everything here is admin-only: a snapshot restore repoints every content
7
+ // node's published pointer at once — system-level blast radius, same
8
+ // gating tier as user management.
9
+ export const snapshotRoutes = async (app, opts) => {
10
+ const snapshotService = new SnapshotService(opts.adapter);
11
+ app.post("/api/snapshots", {
12
+ onRequest: requireAdmin,
13
+ schema: {
14
+ body: CreateSnapshotRequestSchema,
15
+ response: {
16
+ 201: SnapshotResponseSchema,
17
+ 401: ApiErrorSchema,
18
+ 403: ApiErrorSchema,
19
+ },
20
+ },
21
+ }, async (request, reply) => {
22
+ const { items: _items, ...meta } = await snapshotService.createSnapshot(request.body.label, request.user.sub, request.body.description ?? null);
23
+ return reply.code(201).send({ snapshot: meta });
24
+ });
25
+ app.get("/api/snapshots", {
26
+ onRequest: requireAdmin,
27
+ schema: {
28
+ querystring: SnapshotListQuerySchema,
29
+ response: {
30
+ 200: SnapshotListResponseSchema,
31
+ 401: ApiErrorSchema,
32
+ 403: ApiErrorSchema,
33
+ },
34
+ },
35
+ }, async (request) => {
36
+ const { cursor, limit } = request.query;
37
+ const items = await snapshotService.listSnapshots({ cursor, limit });
38
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
39
+ return { items, nextCursor };
40
+ });
41
+ app.post("/api/snapshots/:id/restore", {
42
+ onRequest: requireAdmin,
43
+ schema: {
44
+ params: SnapshotParamsSchema,
45
+ response: {
46
+ 200: RestoreSnapshotResponseSchema,
47
+ 401: ApiErrorSchema,
48
+ 403: ApiErrorSchema,
49
+ 404: ApiErrorSchema,
50
+ },
51
+ },
52
+ }, async (request, reply) => {
53
+ try {
54
+ const restored = await snapshotService.restoreSnapshot(request.params.id);
55
+ const { items, ...meta } = restored;
56
+ return { snapshot: meta, restoredItems: items.length };
57
+ }
58
+ catch (error) {
59
+ if (error instanceof Error && /no snapshot found/i.test(error.message)) {
60
+ return reply.code(404).send({ message: error.message });
61
+ }
62
+ throw error;
63
+ }
64
+ });
65
+ };
@@ -0,0 +1,3 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ import type { ContentDb } from "../db/content-db.js";
3
+ export declare const templateRoutes: FastifyPluginAsyncZod<ContentDb>;
@@ -0,0 +1,37 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { ApiErrorSchema, CreateTemplateRequestSchema, CreateTemplateResponseSchema, TemplateListQuerySchema, TemplateListResponseSchema, } from "@velora-cms/api-schemas";
3
+ import { requireAuth } from "../auth/require-auth.js";
4
+ // Mirrors document-types.ts's shape exactly. Templates are genuinely inert
5
+ // data at this point — no rendering engine consumes them yet (per the
6
+ // Document Types tabs/preview/templates plan revision) — this just lets
7
+ // a document type record which named template(s) exist for it.
8
+ export const templateRoutes = async (app, opts) => {
9
+ const { adapter } = opts;
10
+ app.post("/api/templates", {
11
+ onRequest: requireAuth,
12
+ schema: {
13
+ body: CreateTemplateRequestSchema,
14
+ response: {
15
+ 201: CreateTemplateResponseSchema,
16
+ 401: ApiErrorSchema,
17
+ },
18
+ },
19
+ }, async (request, reply) => {
20
+ const template = { id: randomUUID(), ...request.body };
21
+ await adapter.createTemplate(template);
22
+ return reply.code(201).send({ template });
23
+ });
24
+ app.get("/api/templates", {
25
+ schema: {
26
+ querystring: TemplateListQuerySchema,
27
+ response: {
28
+ 200: TemplateListResponseSchema,
29
+ },
30
+ },
31
+ }, async (request) => {
32
+ const { cursor, limit, documentTypeId } = request.query;
33
+ const items = await adapter.listTemplates({ cursor, limit, documentTypeId });
34
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
35
+ return { items, nextCursor };
36
+ });
37
+ };
@@ -0,0 +1,2 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ export declare const themeRoutes: FastifyPluginAsyncZod;
@@ -0,0 +1,30 @@
1
+ import { ThemeListResponseSchema } from "@velora-cms/api-schemas";
2
+ import { prisma } from "../db/prisma.js";
3
+ // Read-only, no auth (the GET /api/plugins posture): theme tokens are
4
+ // cosmetic metadata. Rows are the source of WHICH themes are installed
5
+ // and enabled; the attached manifest is the source of the tokens — a
6
+ // row whose manifest isn't attached in this process (mid-install crash,
7
+ // stale row) is skipped rather than served without tokens.
8
+ //
9
+ // No pagination: installed themes are a handful, never a growing content
10
+ // or media list — the cursor rule (api-design.md) targets those, not this.
11
+ export const themeRoutes = async (app) => {
12
+ app.get("/api/themes", { schema: { response: { 200: ThemeListResponseSchema } } }, async () => {
13
+ const rows = await prisma.pluginRegistry.findMany({
14
+ where: { enabled: true, type: "theme" },
15
+ orderBy: { id: "asc" },
16
+ });
17
+ const items = [];
18
+ for (const row of rows) {
19
+ const manifest = app.pluginRegistry.getManifest(row.pluginId);
20
+ if (!manifest || manifest.type !== "theme")
21
+ continue;
22
+ const tokens = manifest.contributions.themeTokens;
23
+ if (!tokens)
24
+ continue;
25
+ // ThemeTokenOverrides interface vs Record<string, unknown> assignability limitation
26
+ items.push({ pluginId: row.pluginId, name: row.name, author: manifest.author, tokens: tokens });
27
+ }
28
+ return { items };
29
+ });
30
+ };
@@ -0,0 +1,2 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ export declare const usersRoutes: FastifyPluginAsyncZod;