@rakun-kit/core 1.7.2 → 1.7.4

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 (685) hide show
  1. package/README.md +1409 -1394
  2. package/dist/cjs/api/operations/manager-contract.js +8 -0
  3. package/dist/cjs/api/operations/manager-contract.js.map +1 -1
  4. package/dist/cjs/api/operations/manager-manifest.js +5 -0
  5. package/dist/cjs/api/operations/manager-manifest.js.map +1 -1
  6. package/dist/cjs/api/operations/manager.js +4 -0
  7. package/dist/cjs/api/operations/manager.js.map +1 -1
  8. package/dist/cjs/api/routes/manager/delete.js +12 -0
  9. package/dist/cjs/api/routes/manager/delete.js.map +1 -1
  10. package/dist/cjs/api/routes/manager/media/generatePoster.js +103 -0
  11. package/dist/cjs/api/routes/manager/media/generatePoster.js.map +1 -0
  12. package/dist/cjs/api/routes/manager/media/reimportMedia.js +17 -0
  13. package/dist/cjs/api/routes/manager/media/reimportMedia.js.map +1 -1
  14. package/dist/cjs/api/routes/manager/media/replaceMedia.js +15 -2
  15. package/dist/cjs/api/routes/manager/media/replaceMedia.js.map +1 -1
  16. package/dist/cjs/api/routes/manager/media/resolveMediaRecordUrls.js +20 -1
  17. package/dist/cjs/api/routes/manager/media/resolveMediaRecordUrls.js.map +1 -1
  18. package/dist/cjs/api/routes/manager/update.js +34 -0
  19. package/dist/cjs/api/routes/manager/update.js.map +1 -1
  20. package/dist/cjs/api/utils/authRateLimit.js +35 -2
  21. package/dist/cjs/api/utils/authRateLimit.js.map +1 -1
  22. package/dist/cjs/api/utils/populates/populateRelations.js +16 -1
  23. package/dist/cjs/api/utils/populates/populateRelations.js.map +1 -1
  24. package/dist/cjs/client.js.map +1 -1
  25. package/dist/cjs/collaboration/index.js +7 -1
  26. package/dist/cjs/collaboration/index.js.map +1 -1
  27. package/dist/cjs/collaboration/memory.js +38 -1
  28. package/dist/cjs/collaboration/memory.js.map +1 -1
  29. package/dist/cjs/collaboration/service.js +99 -15
  30. package/dist/cjs/collaboration/service.js.map +1 -1
  31. package/dist/cjs/collaboration/types.js +2 -0
  32. package/dist/cjs/collaboration/types.js.map +1 -1
  33. package/dist/cjs/contracts.js +1 -0
  34. package/dist/cjs/contracts.js.map +1 -1
  35. package/dist/cjs/eventLog/index.js +6 -1
  36. package/dist/cjs/eventLog/index.js.map +1 -1
  37. package/dist/cjs/index.js +28 -6
  38. package/dist/cjs/index.js.map +1 -1
  39. package/dist/cjs/internal-content-types/Media.js +1 -0
  40. package/dist/cjs/internal-content-types/Media.js.map +1 -1
  41. package/dist/cjs/lib/Logger.js +23 -10
  42. package/dist/cjs/lib/Logger.js.map +1 -1
  43. package/dist/cjs/lib/fields/File.js +1 -0
  44. package/dist/cjs/lib/fields/File.js.map +1 -1
  45. package/dist/cjs/mail/index.js +6 -1
  46. package/dist/cjs/mail/index.js.map +1 -1
  47. package/dist/cjs/media/index.js +8 -3
  48. package/dist/cjs/media/index.js.map +1 -1
  49. package/dist/cjs/media/videoPoster.js +45 -0
  50. package/dist/cjs/media/videoPoster.js.map +1 -0
  51. package/dist/cjs/orm/database.js +22 -4
  52. package/dist/cjs/orm/database.js.map +1 -1
  53. package/dist/cjs/orm/index.js +5 -1
  54. package/dist/cjs/orm/index.js.map +1 -1
  55. package/dist/cjs/schemas/manager/media/finalizeUpload.js +2 -0
  56. package/dist/cjs/schemas/manager/media/finalizeUpload.js.map +1 -1
  57. package/dist/cjs/schemas/manager/media/generatePoster.js +16 -0
  58. package/dist/cjs/schemas/manager/media/generatePoster.js.map +1 -0
  59. package/dist/cjs/translation/index.js +6 -1
  60. package/dist/cjs/translation/index.js.map +1 -1
  61. package/dist/docs/index.md +582 -558
  62. package/dist/esm/api/context.d.ts +1 -1
  63. package/dist/esm/api/context.js +3 -3
  64. package/dist/esm/api/hooks/context.d.ts +1 -1
  65. package/dist/esm/api/hooks/runContentHooks.d.ts +4 -4
  66. package/dist/esm/api/hooks/runContentHooks.js +1 -1
  67. package/dist/esm/api/operations/apiEventLog.d.ts +3 -3
  68. package/dist/esm/api/operations/apiEventLog.js +3 -3
  69. package/dist/esm/api/operations/catalog.d.ts +2 -2
  70. package/dist/esm/api/operations/catalog.js +1 -1
  71. package/dist/esm/api/operations/custom.d.ts +1 -1
  72. package/dist/esm/api/operations/custom.js +1 -1
  73. package/dist/esm/api/operations/index.d.ts +225 -206
  74. package/dist/esm/api/operations/index.d.ts.map +1 -1
  75. package/dist/esm/api/operations/index.js +9 -9
  76. package/dist/esm/api/operations/manager-contract.d.ts +184 -171
  77. package/dist/esm/api/operations/manager-contract.d.ts.map +1 -1
  78. package/dist/esm/api/operations/manager-contract.js +16 -8
  79. package/dist/esm/api/operations/manager-contract.js.map +1 -1
  80. package/dist/esm/api/operations/manager-manifest.d.ts +5 -0
  81. package/dist/esm/api/operations/manager-manifest.d.ts.map +1 -1
  82. package/dist/esm/api/operations/manager-manifest.js +5 -0
  83. package/dist/esm/api/operations/manager-manifest.js.map +1 -1
  84. package/dist/esm/api/operations/manager.d.ts +185 -172
  85. package/dist/esm/api/operations/manager.d.ts.map +1 -1
  86. package/dist/esm/api/operations/manager.js +79 -75
  87. package/dist/esm/api/operations/manager.js.map +1 -1
  88. package/dist/esm/api/operations/tracing.d.ts +1 -1
  89. package/dist/esm/api/operations/tracing.js +2 -2
  90. package/dist/esm/api/operations/types.d.ts +1 -1
  91. package/dist/esm/api/operations/web-contract.d.ts +32 -26
  92. package/dist/esm/api/operations/web-contract.d.ts.map +1 -1
  93. package/dist/esm/api/operations/web-contract.js +3 -3
  94. package/dist/esm/api/operations/web.d.ts +33 -27
  95. package/dist/esm/api/operations/web.d.ts.map +1 -1
  96. package/dist/esm/api/operations/web.js +11 -11
  97. package/dist/esm/api/realtime.d.ts +2 -2
  98. package/dist/esm/api/realtime.js +7 -7
  99. package/dist/esm/api/routes/manager/apiOperations.d.ts +3 -3
  100. package/dist/esm/api/routes/manager/apiOperations.js +5 -5
  101. package/dist/esm/api/routes/manager/auth/accountInfo.d.ts +2 -2
  102. package/dist/esm/api/routes/manager/auth/accountInfo.js +3 -3
  103. package/dist/esm/api/routes/manager/auth/deleteSession.d.ts +2 -2
  104. package/dist/esm/api/routes/manager/auth/deleteSession.js +3 -3
  105. package/dist/esm/api/routes/manager/auth/externalLogin.d.ts +2 -2
  106. package/dist/esm/api/routes/manager/auth/externalLogin.js +8 -8
  107. package/dist/esm/api/routes/manager/auth/getSession.d.ts +7 -7
  108. package/dist/esm/api/routes/manager/auth/login.d.ts +2 -2
  109. package/dist/esm/api/routes/manager/auth/login.js +9 -9
  110. package/dist/esm/api/routes/manager/auth/logout.d.ts +1 -1
  111. package/dist/esm/api/routes/manager/auth/logout.js +3 -3
  112. package/dist/esm/api/routes/manager/auth/markTourSeen.d.ts +8 -8
  113. package/dist/esm/api/routes/manager/auth/markTourSeen.js +3 -3
  114. package/dist/esm/api/routes/manager/auth/mfa/regenerateRecoveryCodes.d.ts +2 -2
  115. package/dist/esm/api/routes/manager/auth/mfa/regenerateRecoveryCodes.js +6 -6
  116. package/dist/esm/api/routes/manager/auth/mfa/verifyRecoveryCode.d.ts +2 -2
  117. package/dist/esm/api/routes/manager/auth/mfa/verifyRecoveryCode.js +8 -8
  118. package/dist/esm/api/routes/manager/auth/passwordIpBlocks.d.ts +2 -2
  119. package/dist/esm/api/routes/manager/auth/passwordIpBlocks.js +8 -8
  120. package/dist/esm/api/routes/manager/auth/passwordRecovery.d.ts +2 -2
  121. package/dist/esm/api/routes/manager/auth/passwordRecovery.js +11 -11
  122. package/dist/esm/api/routes/manager/auth/totp/confirmTotp.d.ts +2 -2
  123. package/dist/esm/api/routes/manager/auth/totp/confirmTotp.js +4 -4
  124. package/dist/esm/api/routes/manager/auth/totp/enrollTotp.d.ts +1 -1
  125. package/dist/esm/api/routes/manager/auth/totp/enrollTotp.js +4 -4
  126. package/dist/esm/api/routes/manager/auth/totp/verifyTotp.d.ts +1 -1
  127. package/dist/esm/api/routes/manager/auth/totp/verifyTotp.js +6 -6
  128. package/dist/esm/api/routes/manager/auth/updateAccount.d.ts +8 -8
  129. package/dist/esm/api/routes/manager/auth/updateAccount.js +3 -3
  130. package/dist/esm/api/routes/manager/auth/updatePassword.d.ts +2 -2
  131. package/dist/esm/api/routes/manager/auth/updatePassword.js +5 -5
  132. package/dist/esm/api/routes/manager/auth/updateTutorialPreferences.d.ts +8 -8
  133. package/dist/esm/api/routes/manager/auth/updateTutorialPreferences.js +3 -3
  134. package/dist/esm/api/routes/manager/auth/webauthn/webauthnAuthOptions.js +4 -4
  135. package/dist/esm/api/routes/manager/auth/webauthn/webauthnAuthVerify.js +7 -7
  136. package/dist/esm/api/routes/manager/auth/webauthn/webauthnRegisterOptions.d.ts +2 -2
  137. package/dist/esm/api/routes/manager/auth/webauthn/webauthnRegisterOptions.js +4 -4
  138. package/dist/esm/api/routes/manager/auth/webauthn/webauthnRegisterVerify.d.ts +1 -1
  139. package/dist/esm/api/routes/manager/auth/webauthn/webauthnRegisterVerify.js +7 -7
  140. package/dist/esm/api/routes/manager/backups/create.d.ts +2 -2
  141. package/dist/esm/api/routes/manager/backups/create.js +2 -2
  142. package/dist/esm/api/routes/manager/backups/list.d.ts +2 -2
  143. package/dist/esm/api/routes/manager/backups/list.js +2 -2
  144. package/dist/esm/api/routes/manager/backups/restore.d.ts +2 -2
  145. package/dist/esm/api/routes/manager/backups/restore.js +4 -4
  146. package/dist/esm/api/routes/manager/comments.d.ts +2 -2
  147. package/dist/esm/api/routes/manager/comments.js +9 -9
  148. package/dist/esm/api/routes/manager/contentCollaboration.d.ts +4 -4
  149. package/dist/esm/api/routes/manager/contentCollaboration.js +7 -7
  150. package/dist/esm/api/routes/manager/contentType.d.ts +16 -16
  151. package/dist/esm/api/routes/manager/contentType.js +3 -3
  152. package/dist/esm/api/routes/manager/contentTypes.d.ts +14 -14
  153. package/dist/esm/api/routes/manager/contentTypes.js +1 -1
  154. package/dist/esm/api/routes/manager/contentVersions.d.ts +2 -2
  155. package/dist/esm/api/routes/manager/contentVersions.js +14 -14
  156. package/dist/esm/api/routes/manager/create.d.ts +5 -5
  157. package/dist/esm/api/routes/manager/create.js +11 -11
  158. package/dist/esm/api/routes/manager/delete.d.ts +2 -2
  159. package/dist/esm/api/routes/manager/delete.d.ts.map +1 -1
  160. package/dist/esm/api/routes/manager/delete.js +27 -15
  161. package/dist/esm/api/routes/manager/delete.js.map +1 -1
  162. package/dist/esm/api/routes/manager/duplicate.d.ts +5 -5
  163. package/dist/esm/api/routes/manager/duplicate.js +7 -7
  164. package/dist/esm/api/routes/manager/favorites.d.ts +2 -2
  165. package/dist/esm/api/routes/manager/favorites.js +5 -5
  166. package/dist/esm/api/routes/manager/get.d.ts +2 -2
  167. package/dist/esm/api/routes/manager/get.js +10 -10
  168. package/dist/esm/api/routes/manager/languages.d.ts +4 -4
  169. package/dist/esm/api/routes/manager/languages.js +1 -1
  170. package/dist/esm/api/routes/manager/list.d.ts +2 -2
  171. package/dist/esm/api/routes/manager/list.js +15 -15
  172. package/dist/esm/api/routes/manager/literals/list.d.ts +2 -2
  173. package/dist/esm/api/routes/manager/literals/list.js +5 -5
  174. package/dist/esm/api/routes/manager/literals/upsert.d.ts +2 -2
  175. package/dist/esm/api/routes/manager/literals/upsert.js +6 -6
  176. package/dist/esm/api/routes/manager/localeVariants.d.ts +49 -49
  177. package/dist/esm/api/routes/manager/localeVariants.js +20 -20
  178. package/dist/esm/api/routes/manager/logs/cleanup.d.ts +2 -2
  179. package/dist/esm/api/routes/manager/logs/cleanup.js +3 -3
  180. package/dist/esm/api/routes/manager/logs/list.d.ts +2 -2
  181. package/dist/esm/api/routes/manager/logs/list.js +2 -2
  182. package/dist/esm/api/routes/manager/media/createFolder.d.ts +2 -2
  183. package/dist/esm/api/routes/manager/media/createFolder.js +7 -7
  184. package/dist/esm/api/routes/manager/media/deleteFolder.d.ts +2 -2
  185. package/dist/esm/api/routes/manager/media/deleteFolder.js +7 -7
  186. package/dist/esm/api/routes/manager/media/deleteMediaStorage.js +2 -2
  187. package/dist/esm/api/routes/manager/media/finalizeUpload.d.ts +2 -2
  188. package/dist/esm/api/routes/manager/media/finalizeUpload.js +12 -12
  189. package/dist/esm/api/routes/manager/media/generatePoster.d.ts +7 -0
  190. package/dist/esm/api/routes/manager/media/generatePoster.d.ts.map +1 -0
  191. package/dist/esm/api/routes/manager/media/generatePoster.js +99 -0
  192. package/dist/esm/api/routes/manager/media/generatePoster.js.map +1 -0
  193. package/dist/esm/api/routes/manager/media/getMediaUrl.d.ts +2 -2
  194. package/dist/esm/api/routes/manager/media/getMediaUrl.js +6 -6
  195. package/dist/esm/api/routes/manager/media/listFolders.d.ts +2 -2
  196. package/dist/esm/api/routes/manager/media/listFolders.js +4 -4
  197. package/dist/esm/api/routes/manager/media/prepareUpload.d.ts +3 -3
  198. package/dist/esm/api/routes/manager/media/prepareUpload.js +5 -5
  199. package/dist/esm/api/routes/manager/media/reimportMedia.d.ts +5 -5
  200. package/dist/esm/api/routes/manager/media/reimportMedia.d.ts.map +1 -1
  201. package/dist/esm/api/routes/manager/media/reimportMedia.js +27 -10
  202. package/dist/esm/api/routes/manager/media/reimportMedia.js.map +1 -1
  203. package/dist/esm/api/routes/manager/media/replaceMedia.d.ts +5 -3
  204. package/dist/esm/api/routes/manager/media/replaceMedia.d.ts.map +1 -1
  205. package/dist/esm/api/routes/manager/media/replaceMedia.js +25 -13
  206. package/dist/esm/api/routes/manager/media/replaceMedia.js.map +1 -1
  207. package/dist/esm/api/routes/manager/media/resolveMediaRecordUrls.d.ts.map +1 -1
  208. package/dist/esm/api/routes/manager/media/resolveMediaRecordUrls.js +21 -2
  209. package/dist/esm/api/routes/manager/media/resolveMediaRecordUrls.js.map +1 -1
  210. package/dist/esm/api/routes/manager/media/uploadBinary.d.ts +1 -1
  211. package/dist/esm/api/routes/manager/media/uploadBinary.js +9 -9
  212. package/dist/esm/api/routes/manager/migrations/list.d.ts +2 -2
  213. package/dist/esm/api/routes/manager/migrations/list.js +2 -2
  214. package/dist/esm/api/routes/manager/notifications.d.ts +2 -2
  215. package/dist/esm/api/routes/manager/notifications.js +7 -7
  216. package/dist/esm/api/routes/manager/permissions.d.ts +1 -1
  217. package/dist/esm/api/routes/manager/permissions.js +2 -2
  218. package/dist/esm/api/routes/manager/preview/create.d.ts +2 -2
  219. package/dist/esm/api/routes/manager/preview/create.js +15 -15
  220. package/dist/esm/api/routes/manager/previewSlugRedirects.d.ts +2 -2
  221. package/dist/esm/api/routes/manager/previewSlugRedirects.js +3 -3
  222. package/dist/esm/api/routes/manager/regenerateRoutes.d.ts +1 -1
  223. package/dist/esm/api/routes/manager/regenerateRoutes.js +2 -2
  224. package/dist/esm/api/routes/manager/reviewPolicies.d.ts +2 -2
  225. package/dist/esm/api/routes/manager/reviewPolicies.js +7 -7
  226. package/dist/esm/api/routes/manager/reviews.d.ts +2 -2
  227. package/dist/esm/api/routes/manager/reviews.js +8 -8
  228. package/dist/esm/api/routes/manager/routeLayout.d.ts +2 -2
  229. package/dist/esm/api/routes/manager/routeLayout.js +6 -6
  230. package/dist/esm/api/routes/manager/setDefaultLanguage.d.ts +2 -2
  231. package/dist/esm/api/routes/manager/setDefaultLanguage.js +5 -5
  232. package/dist/esm/api/routes/manager/template.d.ts +3 -3
  233. package/dist/esm/api/routes/manager/template.js +10 -10
  234. package/dist/esm/api/routes/manager/templateCollaboration.d.ts +3 -3
  235. package/dist/esm/api/routes/manager/templateCollaboration.js +9 -9
  236. package/dist/esm/api/routes/manager/translateDocument.d.ts +2 -2
  237. package/dist/esm/api/routes/manager/translateDocument.js +10 -10
  238. package/dist/esm/api/routes/manager/trash.d.ts +3 -3
  239. package/dist/esm/api/routes/manager/trash.js +10 -10
  240. package/dist/esm/api/routes/manager/uiLocales.d.ts +2 -2
  241. package/dist/esm/api/routes/manager/uiLocales.js +5 -5
  242. package/dist/esm/api/routes/manager/update.d.ts +5 -5
  243. package/dist/esm/api/routes/manager/update.d.ts.map +1 -1
  244. package/dist/esm/api/routes/manager/update.js +49 -15
  245. package/dist/esm/api/routes/manager/update.js.map +1 -1
  246. package/dist/esm/api/routes/manager/users.d.ts +2 -2
  247. package/dist/esm/api/routes/manager/users.js +2 -2
  248. package/dist/esm/api/routes/manager/versions/get.d.ts +2 -2
  249. package/dist/esm/api/routes/manager/versions/get.js +2 -2
  250. package/dist/esm/api/routes/manager/versions/list.d.ts +2 -2
  251. package/dist/esm/api/routes/manager/versions/list.js +2 -2
  252. package/dist/esm/api/routes/manager/versions/restore.d.ts +2 -2
  253. package/dist/esm/api/routes/manager/versions/restore.js +8 -8
  254. package/dist/esm/api/routes/web/llms.d.ts +1 -1
  255. package/dist/esm/api/routes/web/llms.js +7 -7
  256. package/dist/esm/api/routes/web/page.d.ts +5 -5
  257. package/dist/esm/api/routes/web/page.js +21 -21
  258. package/dist/esm/api/routes/web/previewPage.d.ts +11 -8
  259. package/dist/esm/api/routes/web/previewPage.d.ts.map +1 -1
  260. package/dist/esm/api/routes/web/previewPage.js +9 -9
  261. package/dist/esm/api/routes/web/robots.d.ts +1 -1
  262. package/dist/esm/api/routes/web/robots.js +2 -2
  263. package/dist/esm/api/routes/web/seo.d.ts +1 -1
  264. package/dist/esm/api/routes/web/seo.js +1 -1
  265. package/dist/esm/api/routes/web/sitemap.d.ts +1 -1
  266. package/dist/esm/api/routes/web/sitemap.js +4 -4
  267. package/dist/esm/api/routes/web/staticPaths.d.ts +3 -3
  268. package/dist/esm/api/routes/web/staticPaths.js +4 -4
  269. package/dist/esm/api/sessionCookie.d.ts +1 -1
  270. package/dist/esm/api/utils/authEvents.d.ts +2 -2
  271. package/dist/esm/api/utils/authEvents.js +2 -2
  272. package/dist/esm/api/utils/authRateLimit.d.ts +2 -1
  273. package/dist/esm/api/utils/authRateLimit.d.ts.map +1 -1
  274. package/dist/esm/api/utils/authRateLimit.js +34 -2
  275. package/dist/esm/api/utils/authRateLimit.js.map +1 -1
  276. package/dist/esm/api/utils/breadcrums.d.ts +5 -5
  277. package/dist/esm/api/utils/breadcrums.js +3 -3
  278. package/dist/esm/api/utils/checkFailureCase.d.ts +1 -1
  279. package/dist/esm/api/utils/checkFailureCase.js +1 -1
  280. package/dist/esm/api/utils/checkOwnership.d.ts +2 -2
  281. package/dist/esm/api/utils/checkOwnership.js +5 -5
  282. package/dist/esm/api/utils/checkPermissions.d.ts +2 -2
  283. package/dist/esm/api/utils/checkPermissions.js +3 -3
  284. package/dist/esm/api/utils/contentTemplate.d.ts +2 -2
  285. package/dist/esm/api/utils/contentTemplate.js +8 -8
  286. package/dist/esm/api/utils/dynamicData.d.ts +5 -5
  287. package/dist/esm/api/utils/dynamicData.js +11 -11
  288. package/dist/esm/api/utils/getLanguages.d.ts +2 -2
  289. package/dist/esm/api/utils/getLanguages.js +2 -2
  290. package/dist/esm/api/utils/getLink.d.ts +2 -2
  291. package/dist/esm/api/utils/getLink.js +5 -5
  292. package/dist/esm/api/utils/getUser.d.ts +7 -7
  293. package/dist/esm/api/utils/getUser.js +4 -4
  294. package/dist/esm/api/utils/index.d.ts +25 -25
  295. package/dist/esm/api/utils/index.js +25 -25
  296. package/dist/esm/api/utils/iteratorVisibility.d.ts +1 -1
  297. package/dist/esm/api/utils/iteratorVisibility.js +1 -1
  298. package/dist/esm/api/utils/localeVariants.d.ts +1 -1
  299. package/dist/esm/api/utils/localeVariants.js +8 -8
  300. package/dist/esm/api/utils/managerNotifications.d.ts +5 -5
  301. package/dist/esm/api/utils/managerNotifications.js +4 -4
  302. package/dist/esm/api/utils/mediaUploadToken.d.ts +1 -1
  303. package/dist/esm/api/utils/mediaUploadToken.js +1 -1
  304. package/dist/esm/api/utils/parseId.d.ts +1 -1
  305. package/dist/esm/api/utils/parseId.js +1 -1
  306. package/dist/esm/api/utils/passwords.js +2 -2
  307. package/dist/esm/api/utils/populates/populateLinks.d.ts +2 -2
  308. package/dist/esm/api/utils/populates/populateLinks.js +5 -5
  309. package/dist/esm/api/utils/populates/populateRelations.d.ts +2 -2
  310. package/dist/esm/api/utils/populates/populateRelations.js +23 -8
  311. package/dist/esm/api/utils/populates/populateRelations.js.map +1 -1
  312. package/dist/esm/api/utils/previewToken.js +1 -1
  313. package/dist/esm/api/utils/realtime.js +1 -1
  314. package/dist/esm/api/utils/recoveryCodes.js +1 -1
  315. package/dist/esm/api/utils/redirects/createSlugChangeRedirects.d.ts +2 -2
  316. package/dist/esm/api/utils/redirects/createSlugChangeRedirects.js +6 -6
  317. package/dist/esm/api/utils/redirects/resolveRedirect.js +2 -2
  318. package/dist/esm/api/utils/redirects/slugPathChanges.d.ts +1 -1
  319. package/dist/esm/api/utils/redirects/slugPathChanges.js +4 -4
  320. package/dist/esm/api/utils/requireContentType.d.ts +1 -1
  321. package/dist/esm/api/utils/requireContentType.js +2 -2
  322. package/dist/esm/api/utils/reviews.d.ts +12 -12
  323. package/dist/esm/api/utils/reviews.js +4 -4
  324. package/dist/esm/api/utils/routes/revalidatePath.js +5 -5
  325. package/dist/esm/api/utils/routes/routeDefinitions.d.ts +1 -1
  326. package/dist/esm/api/utils/routes/routeMapHelpers.d.ts +6 -6
  327. package/dist/esm/api/utils/routes/routeMapHelpers.js +8 -8
  328. package/dist/esm/api/utils/routes/syncConfiguredRoutes.d.ts +2 -2
  329. package/dist/esm/api/utils/routes/syncConfiguredRoutes.js +5 -5
  330. package/dist/esm/api/utils/routes/updateRoutesMap.d.ts +2 -2
  331. package/dist/esm/api/utils/routes/updateRoutesMap.js +5 -5
  332. package/dist/esm/api/utils/safeManagerQuery.d.ts +2 -2
  333. package/dist/esm/api/utils/safeManagerQuery.js +2 -2
  334. package/dist/esm/api/utils/sanitizeManagerOutput.d.ts +1 -1
  335. package/dist/esm/api/utils/sanitizeManagerOutput.js +2 -2
  336. package/dist/esm/api/utils/transformObjectIdsToStrings.js +1 -1
  337. package/dist/esm/api/utils/transformStringToObjectIds.js +1 -1
  338. package/dist/esm/api/utils/translate.d.ts +1 -1
  339. package/dist/esm/api/utils/translate.js +3 -3
  340. package/dist/esm/api/utils/validateModule.d.ts +1 -1
  341. package/dist/esm/api/utils/validateModule.js +2 -2
  342. package/dist/esm/auth/accountRecovery.d.ts +1 -1
  343. package/dist/esm/auth/completePrimaryAuthentication.d.ts +1 -1
  344. package/dist/esm/auth/completePrimaryAuthentication.js +4 -4
  345. package/dist/esm/auth/loginAdapters.d.ts +1 -1
  346. package/dist/esm/auth/passwordFail2ban.d.ts +2 -2
  347. package/dist/esm/auth/passwordFail2ban.js +4 -4
  348. package/dist/esm/auth/passwordResetMailTemplate.d.ts +2 -2
  349. package/dist/esm/auth/passwordResetMailTemplate.js +1 -1
  350. package/dist/esm/bootstrapState.d.ts +16 -16
  351. package/dist/esm/client.d.ts +69 -68
  352. package/dist/esm/client.d.ts.map +1 -1
  353. package/dist/esm/client.js +36 -36
  354. package/dist/esm/client.js.map +1 -1
  355. package/dist/esm/collaboration/document.js +1 -1
  356. package/dist/esm/collaboration/index.d.ts +9 -8
  357. package/dist/esm/collaboration/index.d.ts.map +1 -1
  358. package/dist/esm/collaboration/index.js +9 -4
  359. package/dist/esm/collaboration/index.js.map +1 -1
  360. package/dist/esm/collaboration/memory.d.ts +1 -1
  361. package/dist/esm/collaboration/memory.d.ts.map +1 -1
  362. package/dist/esm/collaboration/memory.js +38 -1
  363. package/dist/esm/collaboration/memory.js.map +1 -1
  364. package/dist/esm/collaboration/service.d.ts +2 -1
  365. package/dist/esm/collaboration/service.d.ts.map +1 -1
  366. package/dist/esm/collaboration/service.js +100 -16
  367. package/dist/esm/collaboration/service.js.map +1 -1
  368. package/dist/esm/collaboration/types.d.ts +4 -0
  369. package/dist/esm/collaboration/types.d.ts.map +1 -1
  370. package/dist/esm/collaboration/types.js +1 -1
  371. package/dist/esm/collaboration/types.js.map +1 -1
  372. package/dist/esm/contracts.d.ts +63 -62
  373. package/dist/esm/contracts.d.ts.map +1 -1
  374. package/dist/esm/contracts.js +63 -62
  375. package/dist/esm/contracts.js.map +1 -1
  376. package/dist/esm/eventLog/index.d.ts +7 -6
  377. package/dist/esm/eventLog/index.d.ts.map +1 -1
  378. package/dist/esm/eventLog/index.js +8 -4
  379. package/dist/esm/eventLog/index.js.map +1 -1
  380. package/dist/esm/eventLog/mongo.d.ts +1 -1
  381. package/dist/esm/eventLog/mongo.js +2 -2
  382. package/dist/esm/eventLog/service.d.ts +1 -1
  383. package/dist/esm/index.d.ts +58 -55
  384. package/dist/esm/index.d.ts.map +1 -1
  385. package/dist/esm/index.js +81 -61
  386. package/dist/esm/index.js.map +1 -1
  387. package/dist/esm/internal-content-types/ApiOperation.d.ts +162 -162
  388. package/dist/esm/internal-content-types/ApiOperation.js +2 -2
  389. package/dist/esm/internal-content-types/Backup.d.ts +199 -199
  390. package/dist/esm/internal-content-types/Backup.js +2 -2
  391. package/dist/esm/internal-content-types/BackupDocument.d.ts +162 -162
  392. package/dist/esm/internal-content-types/BackupDocument.js +2 -2
  393. package/dist/esm/internal-content-types/ContentComment.d.ts +1789 -1789
  394. package/dist/esm/internal-content-types/ContentComment.js +3 -3
  395. package/dist/esm/internal-content-types/ContentCommentReadState.d.ts +1253 -1253
  396. package/dist/esm/internal-content-types/ContentCommentReadState.js +3 -3
  397. package/dist/esm/internal-content-types/ContentReview.d.ts +5858 -5858
  398. package/dist/esm/internal-content-types/ContentReview.js +4 -4
  399. package/dist/esm/internal-content-types/ContentReviewDecision.d.ts +1245 -1245
  400. package/dist/esm/internal-content-types/ContentReviewDecision.js +3 -3
  401. package/dist/esm/internal-content-types/ContentReviewPolicy.d.ts +516 -516
  402. package/dist/esm/internal-content-types/ContentReviewPolicy.js +3 -3
  403. package/dist/esm/internal-content-types/ContentTemplate.d.ts +106 -106
  404. package/dist/esm/internal-content-types/ContentTemplate.js +2 -2
  405. package/dist/esm/internal-content-types/ContentVersion.d.ts +338 -338
  406. package/dist/esm/internal-content-types/ContentVersion.js +2 -2
  407. package/dist/esm/internal-content-types/HelloWorld.d.ts +42 -42
  408. package/dist/esm/internal-content-types/HelloWorld.js +2 -2
  409. package/dist/esm/internal-content-types/Language.d.ts +81 -81
  410. package/dist/esm/internal-content-types/Language.js +2 -2
  411. package/dist/esm/internal-content-types/LiteralTranslation.d.ts +83 -83
  412. package/dist/esm/internal-content-types/LiteralTranslation.js +2 -2
  413. package/dist/esm/internal-content-types/LlmsSettings.d.ts +762 -762
  414. package/dist/esm/internal-content-types/LlmsSettings.js +2 -2
  415. package/dist/esm/internal-content-types/LoginIpBlock.d.ts +98 -98
  416. package/dist/esm/internal-content-types/LoginIpBlock.js +2 -2
  417. package/dist/esm/internal-content-types/ManagerFavorite.d.ts +1213 -1213
  418. package/dist/esm/internal-content-types/ManagerFavorite.js +3 -3
  419. package/dist/esm/internal-content-types/ManagerNotification.d.ts +2500 -2500
  420. package/dist/esm/internal-content-types/ManagerNotification.js +3 -3
  421. package/dist/esm/internal-content-types/ManagerRole.d.ts +47 -47
  422. package/dist/esm/internal-content-types/ManagerRole.js +2 -2
  423. package/dist/esm/internal-content-types/ManagerRoleHooks.d.ts +2 -2
  424. package/dist/esm/internal-content-types/ManagerRoleHooks.js +3 -3
  425. package/dist/esm/internal-content-types/ManagerUser.d.ts +532 -532
  426. package/dist/esm/internal-content-types/ManagerUser.js +3 -3
  427. package/dist/esm/internal-content-types/ManagerUserHooks.d.ts +2 -2
  428. package/dist/esm/internal-content-types/ManagerUserHooks.js +3 -3
  429. package/dist/esm/internal-content-types/Media.d.ts +1457 -1187
  430. package/dist/esm/internal-content-types/Media.d.ts.map +1 -1
  431. package/dist/esm/internal-content-types/Media.js +4 -3
  432. package/dist/esm/internal-content-types/Media.js.map +1 -1
  433. package/dist/esm/internal-content-types/MediaFolder.d.ts +192 -192
  434. package/dist/esm/internal-content-types/MediaFolder.js +2 -2
  435. package/dist/esm/internal-content-types/MfaChallenge.d.ts +1301 -1301
  436. package/dist/esm/internal-content-types/MfaChallenge.js +3 -3
  437. package/dist/esm/internal-content-types/Migration.d.ts +330 -330
  438. package/dist/esm/internal-content-types/Migration.js +2 -2
  439. package/dist/esm/internal-content-types/MigrationLock.d.ts +18 -18
  440. package/dist/esm/internal-content-types/MigrationLock.js +2 -2
  441. package/dist/esm/internal-content-types/Page.d.ts +121 -121
  442. package/dist/esm/internal-content-types/Page.js +3 -3
  443. package/dist/esm/internal-content-types/PasswordResetToken.d.ts +1205 -1205
  444. package/dist/esm/internal-content-types/PasswordResetToken.js +4 -4
  445. package/dist/esm/internal-content-types/PreviewSnapshot.d.ts +378 -378
  446. package/dist/esm/internal-content-types/PreviewSnapshot.js +2 -2
  447. package/dist/esm/internal-content-types/Redirect.d.ts +378 -378
  448. package/dist/esm/internal-content-types/Redirect.js +2 -2
  449. package/dist/esm/internal-content-types/RobotsRule.d.ts +250 -250
  450. package/dist/esm/internal-content-types/RobotsRule.js +2 -2
  451. package/dist/esm/internal-content-types/Route.d.ts +232 -232
  452. package/dist/esm/internal-content-types/Route.js +2 -2
  453. package/dist/esm/internal-content-types/RouteLayoutModule.d.ts +266 -266
  454. package/dist/esm/internal-content-types/RouteLayoutModule.js +2 -2
  455. package/dist/esm/internal-content-types/RouteLayoutModuleOverride.d.ts +242 -242
  456. package/dist/esm/internal-content-types/RouteLayoutModuleOverride.js +2 -2
  457. package/dist/esm/internal-content-types/RouteLocaleVariant.d.ts +242 -242
  458. package/dist/esm/internal-content-types/RouteLocaleVariant.js +2 -2
  459. package/dist/esm/internal-content-types/RouteMap.d.ts +258 -258
  460. package/dist/esm/internal-content-types/RouteMap.js +2 -2
  461. package/dist/esm/internal-content-types/RouteSettings.d.ts +340 -340
  462. package/dist/esm/internal-content-types/RouteSettings.js +3 -3
  463. package/dist/esm/internal-content-types/SchemaState.d.ts +82 -82
  464. package/dist/esm/internal-content-types/SchemaState.js +2 -2
  465. package/dist/esm/internal-content-types/Seo.d.ts +799 -738
  466. package/dist/esm/internal-content-types/Seo.d.ts.map +1 -1
  467. package/dist/esm/internal-content-types/Seo.js +2 -2
  468. package/dist/esm/internal-content-types/SeoAudit.d.ts +418 -418
  469. package/dist/esm/internal-content-types/SeoAudit.js +2 -2
  470. package/dist/esm/internal-content-types/SeoSettings.d.ts +2125 -1982
  471. package/dist/esm/internal-content-types/SeoSettings.d.ts.map +1 -1
  472. package/dist/esm/internal-content-types/SeoSettings.js +3 -3
  473. package/dist/esm/internal-content-types/Session.d.ts +1189 -1189
  474. package/dist/esm/internal-content-types/Session.js +3 -3
  475. package/dist/esm/internal-content-types/StructuredData.d.ts +734 -694
  476. package/dist/esm/internal-content-types/StructuredData.d.ts.map +1 -1
  477. package/dist/esm/internal-content-types/StructuredData.js +2 -2
  478. package/dist/esm/internal-content-types/TemplateContent.d.ts +1 -1
  479. package/dist/esm/internal-content-types/TemplateContent.js +1 -1
  480. package/dist/esm/internal-content-types/UserMfa.d.ts +1290 -1290
  481. package/dist/esm/internal-content-types/UserMfa.js +4 -4
  482. package/dist/esm/internal-content-types/WebAuthnCredential.d.ts +1317 -1317
  483. package/dist/esm/internal-content-types/WebAuthnCredential.js +3 -3
  484. package/dist/esm/internal-content-types/WebAuthnRegChallenge.d.ts +1245 -1245
  485. package/dist/esm/internal-content-types/WebAuthnRegChallenge.js +3 -3
  486. package/dist/esm/internal-content-types/index.d.ts +45 -45
  487. package/dist/esm/internal-content-types/index.js +45 -45
  488. package/dist/esm/internal-content-types/syncAdminRole.d.ts +4 -4
  489. package/dist/esm/internal-content-types/syncAdminRole.js +4 -4
  490. package/dist/esm/lib/ContentType.d.ts +7 -7
  491. package/dist/esm/lib/ContentType.js +5 -5
  492. package/dist/esm/lib/Logger.d.ts +1 -0
  493. package/dist/esm/lib/Logger.d.ts.map +1 -1
  494. package/dist/esm/lib/Logger.js +22 -10
  495. package/dist/esm/lib/Logger.js.map +1 -1
  496. package/dist/esm/lib/Permissions.d.ts +2 -2
  497. package/dist/esm/lib/Permissions.js +4 -4
  498. package/dist/esm/lib/Registry.d.ts +6 -6
  499. package/dist/esm/lib/Registry.js +4 -4
  500. package/dist/esm/lib/errors/AppError.d.ts +1 -1
  501. package/dist/esm/lib/errors/AppError.js +1 -1
  502. package/dist/esm/lib/errors/index.d.ts +4 -4
  503. package/dist/esm/lib/errors/index.js +3 -3
  504. package/dist/esm/lib/errors/throwAppError.d.ts +1 -1
  505. package/dist/esm/lib/errors/throwAppError.js +3 -3
  506. package/dist/esm/lib/fields/Boolean.d.ts +1 -1
  507. package/dist/esm/lib/fields/Boolean.js +1 -1
  508. package/dist/esm/lib/fields/Breadcrums.d.ts +1 -1
  509. package/dist/esm/lib/fields/Breadcrums.js +1 -1
  510. package/dist/esm/lib/fields/ContentReference.d.ts +2 -2
  511. package/dist/esm/lib/fields/ContentReference.js +2 -2
  512. package/dist/esm/lib/fields/Date.d.ts +1 -1
  513. package/dist/esm/lib/fields/Date.js +1 -1
  514. package/dist/esm/lib/fields/Field.d.ts +15 -15
  515. package/dist/esm/lib/fields/Fields.d.ts +15 -15
  516. package/dist/esm/lib/fields/Fields.js +15 -15
  517. package/dist/esm/lib/fields/File.d.ts +2 -1
  518. package/dist/esm/lib/fields/File.d.ts.map +1 -1
  519. package/dist/esm/lib/fields/File.js +3 -2
  520. package/dist/esm/lib/fields/File.js.map +1 -1
  521. package/dist/esm/lib/fields/Iterator.d.ts +3 -3
  522. package/dist/esm/lib/fields/Iterator.js +3 -3
  523. package/dist/esm/lib/fields/Link.d.ts +1 -1
  524. package/dist/esm/lib/fields/Link.js +2 -2
  525. package/dist/esm/lib/fields/List.d.ts +1 -1
  526. package/dist/esm/lib/fields/List.js +2 -2
  527. package/dist/esm/lib/fields/Menu.d.ts +2 -2
  528. package/dist/esm/lib/fields/Menu.js +2 -2
  529. package/dist/esm/lib/fields/Number.d.ts +1 -1
  530. package/dist/esm/lib/fields/Number.js +1 -1
  531. package/dist/esm/lib/fields/Relation.d.ts +3 -3
  532. package/dist/esm/lib/fields/Relation.js +3 -3
  533. package/dist/esm/lib/fields/Select.d.ts +1 -1
  534. package/dist/esm/lib/fields/Select.js +1 -1
  535. package/dist/esm/lib/fields/SelfRelation.d.ts +1 -1
  536. package/dist/esm/lib/fields/SelfRelation.js +2 -2
  537. package/dist/esm/lib/fields/SimpleList.d.ts +1 -1
  538. package/dist/esm/lib/fields/SimpleList.js +1 -1
  539. package/dist/esm/lib/fields/String.d.ts +1 -1
  540. package/dist/esm/lib/fields/String.js +3 -3
  541. package/dist/esm/lib/fields/index.d.ts +17 -17
  542. package/dist/esm/lib/fields/index.js +17 -17
  543. package/dist/esm/lib/hooks.d.ts +4 -4
  544. package/dist/esm/lib/routeableContent.js +1 -1
  545. package/dist/esm/lib/types/index.d.ts +2 -2
  546. package/dist/esm/lib/utils/getListField.d.ts +1 -1
  547. package/dist/esm/lib/utils/getSchemas.d.ts +1 -1
  548. package/dist/esm/lib/utils/getTranslation.d.ts +2 -2
  549. package/dist/esm/lib/utils/getTranslation.js +1 -1
  550. package/dist/esm/lib/utils/isTranslatableObject.d.ts +1 -1
  551. package/dist/esm/lib/utils/translateObject.d.ts +2 -2
  552. package/dist/esm/lib/utils/translateObject.js +3 -3
  553. package/dist/esm/literals/definitions.d.ts +1 -1
  554. package/dist/esm/literals/definitions.js +1 -1
  555. package/dist/esm/literals/index.d.ts +2 -2
  556. package/dist/esm/literals/index.js +2 -2
  557. package/dist/esm/mail/index.d.ts +7 -6
  558. package/dist/esm/mail/index.d.ts.map +1 -1
  559. package/dist/esm/mail/index.js +9 -5
  560. package/dist/esm/mail/index.js.map +1 -1
  561. package/dist/esm/mail/mailService.d.ts +2 -2
  562. package/dist/esm/mail/mailService.js +2 -2
  563. package/dist/esm/mail/templates.d.ts +1 -1
  564. package/dist/esm/mail/templates.js +2 -2
  565. package/dist/esm/manager.d.ts +8 -3
  566. package/dist/esm/manager.d.ts.map +1 -1
  567. package/dist/esm/manager.js +1 -1
  568. package/dist/esm/media/imageOptimization.d.ts +1 -1
  569. package/dist/esm/media/imageOptimization.js +2 -2
  570. package/dist/esm/media/index.d.ts +6 -5
  571. package/dist/esm/media/index.d.ts.map +1 -1
  572. package/dist/esm/media/index.js +10 -6
  573. package/dist/esm/media/index.js.map +1 -1
  574. package/dist/esm/media/mediaOptimization.d.ts +2 -2
  575. package/dist/esm/media/mediaOptimization.js +2 -2
  576. package/dist/esm/media/mediaService.d.ts +1 -1
  577. package/dist/esm/media/mediaService.js +1 -1
  578. package/dist/esm/media/publicMedia.js +1 -1
  579. package/dist/esm/media/videoOptimization.d.ts +2 -2
  580. package/dist/esm/media/videoOptimization.js +3 -3
  581. package/dist/esm/media/videoPoster.d.ts +2 -0
  582. package/dist/esm/media/videoPoster.d.ts.map +1 -0
  583. package/dist/esm/media/videoPoster.js +38 -0
  584. package/dist/esm/media/videoPoster.js.map +1 -0
  585. package/dist/esm/orm/backups.js +5 -5
  586. package/dist/esm/orm/createIndexes.js +4 -4
  587. package/dist/esm/orm/database.d.ts +3 -1
  588. package/dist/esm/orm/database.d.ts.map +1 -1
  589. package/dist/esm/orm/database.js +25 -7
  590. package/dist/esm/orm/database.js.map +1 -1
  591. package/dist/esm/orm/dbService.d.ts +6 -6
  592. package/dist/esm/orm/index.d.ts +2 -2
  593. package/dist/esm/orm/index.d.ts.map +1 -1
  594. package/dist/esm/orm/index.js +21 -17
  595. package/dist/esm/orm/index.js.map +1 -1
  596. package/dist/esm/orm/migrations.d.ts +1 -1
  597. package/dist/esm/orm/migrations.js +4 -4
  598. package/dist/esm/orm/mongodbPeer.js +1 -1
  599. package/dist/esm/orm/operations/clear.d.ts +2 -2
  600. package/dist/esm/orm/operations/clear.js +1 -1
  601. package/dist/esm/orm/operations/create.d.ts +4 -4
  602. package/dist/esm/orm/operations/create.js +7 -7
  603. package/dist/esm/orm/operations/delete.d.ts +3 -3
  604. package/dist/esm/orm/operations/delete.js +5 -5
  605. package/dist/esm/orm/operations/find.d.ts +2 -2
  606. package/dist/esm/orm/operations/find.js +3 -3
  607. package/dist/esm/orm/operations/findDependencies.d.ts +2 -2
  608. package/dist/esm/orm/operations/findDependencies.js +4 -4
  609. package/dist/esm/orm/operations/get.d.ts +3 -3
  610. package/dist/esm/orm/operations/get.js +3 -3
  611. package/dist/esm/orm/operations/getAll.d.ts +2 -2
  612. package/dist/esm/orm/operations/getAll.js +2 -2
  613. package/dist/esm/orm/operations/list.d.ts +2 -2
  614. package/dist/esm/orm/operations/list.js +3 -3
  615. package/dist/esm/orm/operations/update.d.ts +5 -5
  616. package/dist/esm/orm/operations/update.js +8 -8
  617. package/dist/esm/orm/operations/updateMany.d.ts +4 -4
  618. package/dist/esm/orm/operations/updateMany.js +4 -4
  619. package/dist/esm/orm/operations/upsert.d.ts +3 -3
  620. package/dist/esm/orm/operations/upsert.js +4 -4
  621. package/dist/esm/orm/utils/deepDeleteNulls.js +1 -1
  622. package/dist/esm/orm/utils/parseId.d.ts +1 -1
  623. package/dist/esm/orm/utils/parseId.js +1 -1
  624. package/dist/esm/orm/utils/transformObjectIdsToStrings.js +1 -1
  625. package/dist/esm/orm/utils/transformStringToObjectIds.js +1 -1
  626. package/dist/esm/orm/versions.d.ts +1 -1
  627. package/dist/esm/orm/versions.js +7 -7
  628. package/dist/esm/platform/image.d.ts +1 -1
  629. package/dist/esm/platform/image.js +1 -1
  630. package/dist/esm/platform/index.d.ts +8 -8
  631. package/dist/esm/platform/index.js +10 -10
  632. package/dist/esm/platform/node.d.ts +1 -1
  633. package/dist/esm/platform/realtime.d.ts +1 -1
  634. package/dist/esm/platform/realtimeServer.d.ts +1 -1
  635. package/dist/esm/platform/runtime.d.ts +1 -1
  636. package/dist/esm/plugins.d.ts +11 -11
  637. package/dist/esm/schemas/manager/auth/externalLogin.js +1 -1
  638. package/dist/esm/schemas/manager/collaborationPresence.js +1 -1
  639. package/dist/esm/schemas/manager/comments.js +1 -1
  640. package/dist/esm/schemas/manager/contentCollaboration.js +1 -1
  641. package/dist/esm/schemas/manager/contentVersions.d.ts +64 -64
  642. package/dist/esm/schemas/manager/contentVersions.js +3 -3
  643. package/dist/esm/schemas/manager/literals/upsert.js +1 -1
  644. package/dist/esm/schemas/manager/localeVariants.d.ts +96 -96
  645. package/dist/esm/schemas/manager/localeVariants.js +1 -1
  646. package/dist/esm/schemas/manager/media/finalizeUpload.d.ts +4 -0
  647. package/dist/esm/schemas/manager/media/finalizeUpload.d.ts.map +1 -1
  648. package/dist/esm/schemas/manager/media/finalizeUpload.js +3 -1
  649. package/dist/esm/schemas/manager/media/finalizeUpload.js.map +1 -1
  650. package/dist/esm/schemas/manager/media/generatePoster.d.ts +12 -0
  651. package/dist/esm/schemas/manager/media/generatePoster.d.ts.map +1 -0
  652. package/dist/esm/schemas/manager/media/generatePoster.js +10 -0
  653. package/dist/esm/schemas/manager/media/generatePoster.js.map +1 -0
  654. package/dist/esm/schemas/manager/media/getMediaUrl.js +1 -1
  655. package/dist/esm/schemas/manager/media/listFolders.js +1 -1
  656. package/dist/esm/schemas/manager/media/reimportMedia.d.ts +3 -1
  657. package/dist/esm/schemas/manager/media/reimportMedia.d.ts.map +1 -1
  658. package/dist/esm/schemas/manager/media/reimportMedia.js +2 -2
  659. package/dist/esm/schemas/manager/media/replaceMedia.d.ts +2 -0
  660. package/dist/esm/schemas/manager/media/replaceMedia.d.ts.map +1 -1
  661. package/dist/esm/schemas/manager/media/replaceMedia.js +1 -1
  662. package/dist/esm/schemas/manager/notifications.js +1 -1
  663. package/dist/esm/schemas/manager/reviews.js +1 -1
  664. package/dist/esm/schemas/manager/templateCollaboration.js +2 -2
  665. package/dist/esm/schemas/manager/uiLocales.js +1 -1
  666. package/dist/esm/schemas/web/page.d.ts +64 -40
  667. package/dist/esm/schemas/web/page.d.ts.map +1 -1
  668. package/dist/esm/schemas/web/page.js +1 -1
  669. package/dist/esm/schemas/web/previewPage.d.ts +40 -28
  670. package/dist/esm/schemas/web/previewPage.d.ts.map +1 -1
  671. package/dist/esm/schemas/web/previewPage.js +1 -1
  672. package/dist/esm/translation/document.d.ts +3 -3
  673. package/dist/esm/translation/document.js +1 -1
  674. package/dist/esm/translation/index.d.ts +5 -4
  675. package/dist/esm/translation/index.d.ts.map +1 -1
  676. package/dist/esm/translation/index.js +7 -3
  677. package/dist/esm/translation/index.js.map +1 -1
  678. package/dist/esm/translation/translationService.d.ts +1 -1
  679. package/dist/esm/web/client.d.ts +1 -1
  680. package/dist/esm/web/client.js +1 -1
  681. package/dist/esm/web/index.d.ts +2 -2
  682. package/dist/esm/web/index.js +2 -2
  683. package/dist/esm/web/page.d.ts +1 -1
  684. package/dist/tsconfig.build.tsbuildinfo +1 -1
  685. package/package.json +1 -1
package/README.md CHANGED
@@ -1,1395 +1,1410 @@
1
- # @rakun-kit/core
2
-
3
- `@rakun-kit/core` contains Rakun's shared domain model and runtime. It defines content types, fields, Zod schemas, manager/web operations, request context, MongoDB connection handling, media service, permissions, translations, and typed contracts used by adapters (`@rakun-kit/next`, `@rakun-kit/express`, `@rakun-kit/trpc`) and the manager client.
4
-
5
- ## Main Entry
6
-
7
- Typical imports:
8
-
9
- ```ts
10
- import { ContentType, f, rakunBootstrap, ensureRakunInitialized } from '@rakun-kit/core'
11
- ```
12
-
13
- The main entrypoint exports:
14
-
15
- - Bootstrap: `rakunBootstrap`, `ensureRakunBootstrap`, `ensureRakunInitialized`, `getRakunBootstrapOptions`.
16
- - Context: `createRequestContext`, `getSessionCookie`, `setSessionCookie`.
17
- - Operations: manager/web contracts and definitions.
18
- - Media: `createMediaService`, `getMediaService`, adapters, and types.
19
- - Mail: `createMailService`, `sendMail`, typed template registries, adapters, and types.
20
- - Permissions, translations, errors, contracts, and public types.
21
- - Basic internal content types: `Language`, `ManagerUser`, `Seo`.
22
-
23
- Relevant subpaths:
24
-
25
- - `@rakun-kit/core/manager`: manager operation metadata and types.
26
- - `@rakun-kit/core/contracts`: shared manager/web payload schemas.
27
- - `@rakun-kit/core/client`: client-facing types and utilities.
28
- - `@rakun-kit/core/logger`: logger.
29
- - `@rakun-kit/core/errors`: application errors.
30
- - `@rakun-kit/core/types`: types derived from content types.
31
-
32
- ## Bootstrap
33
-
34
- `rakunBootstrap(options)` registers global configuration and content types:
35
-
36
- ```ts
37
- rakunBootstrap({
38
- literals,
39
- contentTypes: [Page, Post],
40
- routes,
41
- apiOperations,
42
- mongo: {
43
- MONGO_URI: process.env.MONGO_URI!,
44
- ENVIRONMENT: process.env.NODE_ENV === 'test' ? 'test' : 'production',
45
- },
46
- media,
47
- logger: {
48
- level: 'info',
49
- prettify: true,
50
- },
51
- syncRoutes: true,
52
- })
53
- ```
54
-
55
- Options:
56
-
57
- - `literals`: text/translation catalog.
58
- - `contentTypes`: application content types.
59
- - `internalContentTypes`: optional overrides for Rakun internal content types, currently `Page`.
60
- - `routes`: configured routes used to resolve pages.
61
- - `apiOperations`: custom API operations added to the Rakun operation registry.
62
- - `mongo`: MongoDB connection. Required before serving Rakun requests.
63
- - `media`: media adapter/configuration. Optional.
64
- - `mail`: outbound mail adapter and default sender configuration. Optional.
65
- - `collaboration`: storage adapter for unsaved Yjs content working documents.
66
- Optional; defaults to process memory.
67
- - `accountRecovery`: password-reset URL builder, expiry, and optional custom
68
- mail template. Requires `mail`; core provides the default template.
69
- - `login`: manager password-login toggle and external login adapters.
70
- - `logger`: logger configuration. If omitted, an `info` logger with `prettify` is created.
71
- - `syncRoutes`: syncs configured routes during initialization. Enabled by default.
72
-
73
- ## Runtime Platform
74
-
75
- `rakunBootstrap` accepts an optional resolved `platform`. Without one, Rakun
76
- detects Bun through `process.versions.bun` and otherwise uses Node.js defaults.
77
- Runtime, framework, and deployment are separate values, and every capability
78
- can be overridden without configuring the rest:
79
-
80
- ```ts
81
- import { createPlatform, pollingRealtime, sharpImage } from '@rakun-kit/core'
82
-
83
- rakunBootstrap({
84
- ...options,
85
- platform: createPlatform({
86
- deployment: 'serverless',
87
- image: sharpImage(),
88
- realtime: pollingRealtime({ intervalMs: 5_000 }),
89
- }),
90
- })
91
- ```
92
-
93
- The default image processor is native `Bun.Image` when available and `sharp`
94
- otherwise. Node.js image optimization therefore requires the optional `sharp`
95
- peer. Bun also falls back to it when an OS-dependent codec cannot handle the
96
- requested format. Custom `ImageProcessor` implementations provide
97
- `metadata`, `transform`, and `placeholder`; the latter returns an inline
98
- `{ dataUrl, mime }` LQIP. Bun uses native `Bun.Image.placeholder()`, while
99
- Sharp produces the placeholder through its normal reduced transform and serves
100
- as the fallback when the Bun method is unavailable. The
101
- platform resolves crypto, filesystem, compression,
102
- workers, and realtime capabilities. Polling is the safe realtime default when
103
- the host cannot keep persistent connections open. The Next and Express
104
- adapters automatically serve configured SSE streams.
105
- Calling `sseRealtime()` uses the API-relative `/realtime` endpoint; pass an
106
- explicit `endpoint` only when the public route differs.
107
-
108
- The manager bootstrap exposes only the safe transport metadata from this
109
- provider. Manager mutations publish invalidation topics for collaborative
110
- documents, locale variants, versions, comments, and notifications. Event
111
- transports therefore refresh matching queries on demand; polling keeps the
112
- same behavior through its configured interval.
113
-
114
- Server adapters share `parseRealtimeTopics`, `isRealtimeEndpointRequest`,
115
- `createRealtimeSseStream`, and `authorizeRealtimeSubscription` from core. A
116
- custom HTTP framework can use these primitives without importing either the
117
- Next or Express adapter.
118
-
119
- The built-in SSE provider uses an in-process topic broker. For multiple server
120
- processes or replicas, provide a `RealtimeProvider` backed by a shared broker
121
- while keeping the same transport metadata and subscription contract.
122
-
123
- ## Collaborative content documents
124
-
125
- Every saved content document and draft has an independent Yjs working document
126
- keyed by its content type and `_id`. Editor updates synchronize through the
127
- authenticated `manager.contentCollaboration.sync` operation without changing
128
- the MongoDB content snapshot or triggering revalidation. Save calls
129
- `manager.contentCollaboration.save`, which materializes and validates the
130
- server-side CRDT state before using the normal update and revalidation flow.
131
- Draft promotion remains separate and promotes only a saved draft snapshot.
132
- `manager.contentCollaboration.discard` restores the room from that persisted
133
- snapshot, publishes the reset to connected editors, and marks the restored Yjs
134
- state as saved without creating a new content revision. Shared templates expose
135
- the equivalent `manager.templateCollaboration.discard` operation.
136
-
137
- The default adapter keeps working documents in process memory. Configure a
138
- shared, durable implementation for replicated or restart-durable deployments:
139
-
140
- ```ts
141
- rakunBootstrap({
142
- // ...
143
- collaboration: {
144
- adapter: {
145
- load: async (roomId) => loadRoom(roomId),
146
- save: async (roomId, state) => saveRoom(roomId, state),
147
- loadPresence: async (roomId) => loadRoomPresence(roomId),
148
- savePresence: async (roomId, presence) => saveRoomPresence(roomId, presence),
149
- delete: async (roomId) => deleteRoom(roomId),
150
- },
151
- },
152
- })
153
- ```
154
-
155
- `state.update` and `state.savedStateVector` are opaque `Uint8Array` values.
156
- Store them outside the public content record. The built-in manager applies this
157
- to saved content edit screens and to each content type's shared Template.
158
- Templates use their own `template:<contentType>` room rather than an individual
159
- document room. Settings, route-layout overrides, and other administrative forms
160
- remain local. Translation changes join the working document and are persisted
161
- only by Save.
162
-
163
- The sync response also includes ephemeral viewers keyed by browser-tab client
164
- id, including the focused field. Presence expires automatically and is not
165
- part of `state.update`, `state.savedStateVector`, or the persistent event log.
166
- With SSE, the authenticated subscription owns the tab presence: server
167
- heartbeats renew it and closing the last stream for that tab removes it, so the
168
- manager does not send a periodic collaboration sync request just to stay
169
- visible. Polling refreshes presence through its normal sync cycle.
170
- `loadPresence` and `savePresence` are optional; implement both in replicated
171
- deployments that need presence shared across processes.
172
-
173
- `@rakun-kit/manager-react` additionally stores these Yjs rooms per user in
174
- IndexedDB, allowing cached content and Template fields to remain editable during
175
- temporary connection loss. Reconnection exchanges the local updates with core;
176
- only an online Save commits them to the public content snapshot.
177
-
178
- ## External manager login
179
-
180
- Configure external login methods in `rakunBootstrap`. The callback URL is the
181
- manager's public `/login/callback` route.
182
-
183
- ```ts
184
- import {
185
- createGitHubLoginAdapter,
186
- createGoogleLoginAdapter,
187
- createMicrosoftLoginAdapter,
188
- rakunBootstrap,
189
- } from '@rakun-kit/core'
190
-
191
- const callbackUrl = 'https://cms.example.com/backend/login/callback'
192
-
193
- rakunBootstrap({
194
- // ...
195
- login: {
196
- password: true,
197
- adapters: [
198
- createGitHubLoginAdapter({
199
- clientId: process.env.GITHUB_CLIENT_ID!,
200
- clientSecret: process.env.GITHUB_CLIENT_SECRET!,
201
- redirectUri: callbackUrl,
202
- }),
203
- createGoogleLoginAdapter({
204
- clientId: process.env.GOOGLE_CLIENT_ID!,
205
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
206
- redirectUri: callbackUrl,
207
- }),
208
- createMicrosoftLoginAdapter({
209
- clientId: process.env.MICROSOFT_CLIENT_ID!,
210
- clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
211
- redirectUri: callbackUrl,
212
- tenant: 'common',
213
- }),
214
- ],
215
- },
216
- })
217
- ```
218
-
219
- Register the exact callback URL in each provider console. External identities
220
- must return a verified email matching an
221
- existing manager user. Rakun does not create manager accounts automatically.
222
- The flow uses an HttpOnly state cookie and PKCE and preserves the existing MFA
223
- step. Set `password: false` only when at least one adapter is configured.
224
- Custom providers can be implemented with `defineLoginAdapter`.
225
-
226
- ### Persistent password-login IP blocking
227
-
228
- Password login enables persistent fail2ban-style IP blocking by default. Five
229
- failed attempts block the IP indefinitely; a successful login before reaching
230
- the limit clears its failed-attempt counter. Configure or disable it through
231
- `login.fail2ban`:
232
-
233
- ```ts
234
- rakunBootstrap({
235
- // ...
236
- login: {
237
- password: true,
238
- fail2ban: {
239
- maxAttempts: 5,
240
- // Optional when the deployment uses a custom trusted proxy:
241
- // resolveIp: (ctx) => ctx.req?.headers?.['your-trusted-ip-header'] as string,
242
- },
243
- },
244
- })
245
- ```
246
-
247
- Users with the `auth.ipBlocks.manage` permission can inspect and remove blocks
248
- under Manager Settings → Security. Admin roles receive this built-in permission
249
- automatically. A block can also be removed directly from MongoDB by deleting
250
- the matching document from the `LoginIpBlock` collection. Set `fail2ban: false`
251
- to disable this behavior. External login adapters are not affected.
252
-
253
- ## Account recovery and MFA recovery
254
-
255
- Password recovery is enabled when both `mail` and `accountRecovery` are
256
- configured. The reset URL must point to the manager's public
257
- `/reset-password` screen and preserve the token as the `token` query parameter:
258
-
259
- ```ts
260
- import { rakunBootstrap } from '@rakun-kit/core'
261
- import { createResendMailServiceConfig } from '@rakun-kit/resend'
262
-
263
- rakunBootstrap({
264
- // ...
265
- mail: createResendMailServiceConfig({
266
- apiKey: process.env.RESEND_API_KEY!,
267
- defaultFrom: process.env.RAKUN_MAIL_FROM!,
268
- }),
269
- accountRecovery: {
270
- passwordReset: {
271
- expiresInMs: 60 * 60 * 1000,
272
- createUrl: (token) =>
273
- `https://cms.example.com/backend/reset-password?token=${encodeURIComponent(token)}`,
274
- },
275
- },
276
- })
277
- ```
278
-
279
- Core uses its branded HTML and plain-text password-reset template by default.
280
- Pass `accountRecovery.passwordReset.template` only when the application needs
281
- to replace it with custom branding or copy.
282
-
283
- Reset tokens are random, stored only as SHA-256 hashes, expire, and are consumed
284
- once. A successful reset closes every existing session but never disables MFA.
285
- The request endpoint always returns the same result for known and unknown email
286
- addresses.
287
-
288
- Enabling TOTP or the first WebAuthn device returns one-time recovery codes to
289
- the manager UI. Only their hashes are persisted, each code is consumed after
290
- one login, and users can replace the entire set from their account after
291
- confirming their current password. MFA state cannot be disabled through generic
292
- manager content operations. If a user loses all MFA methods and recovery codes,
293
- recovery must be handled by an authorized person through a channel outside the
294
- CMS.
295
-
296
- Content types can define lifecycle `hooks` and opt into manager-selected
297
- `dynamicData` sources.
298
-
299
- Routes can define fixed layout module slots. Rakun syncs those slots and the
300
- manager lets users select an existing entry for each route:
301
-
302
- ```ts
303
- rakunBootstrap({
304
- // ...
305
- routes: [
306
- {
307
- key: 'pages',
308
- contentType: 'Page',
309
- field: 'slug',
310
- hasPage: true,
311
- dynamic: false,
312
- defaultBasePath: '',
313
- layout: [
314
- { key: 'header', contentType: 'Header' },
315
- { type: 'content' },
316
- { key: 'footer', contentType: 'Footer' },
317
- ],
318
- },
319
- ],
320
- })
321
- ```
322
-
323
- The web page response includes an ordered `layout` array containing module slots
324
- plus the content slot. This is the only module collection in the response:
325
- content modules live in the `modules` property of the `content` layout item and
326
- are not duplicated at the top level.
327
-
328
- The response `info` object contains the page's normal resolved fields plus
329
- route context such as `locale` and `variantGroupId`. Translated website literals
330
- live in the separate top-level `literals` property. Composition-only fields
331
- (`_iterator`, `_seo`, and the legacy `_iteratorUnlinked` marker) are removed
332
- recursively from the page and all populated relations before `info` is sent.
333
-
334
- ## Module picker
335
-
336
- Content types used as iterator modules can customize their card in the manager's
337
- add-module dialog. `modulePicker.preview` accepts an image URL available to the
338
- manager browser; relative URLs are also supported:
339
-
340
- ```ts
341
- const Hero = new ContentType({
342
- name: 'Hero',
343
- modulePicker: {
344
- title: 'Hero section',
345
- description: 'Large introduction with heading, copy, and CTA.',
346
- category: 'Marketing',
347
- icon: 'PanelTop',
348
- preview: '/images/modules/hero.webp',
349
- keywords: ['banner', 'cover'],
350
- },
351
- fields: {
352
- title: f.string().help('content.heroTitleHelp').required(),
353
- },
354
- })
355
- ```
356
-
357
- When `preview` is omitted or cannot be loaded, the picker displays a neutral
358
- placeholder so module cards keep a consistent height.
359
-
360
- Use field `.help(text)` for longer, optional guidance. The manager places a help
361
- icon beside that field and opens the text on demand, including inside iterator
362
- and blocks modules. `text` may be direct copy or a manager translation key
363
- supplied by the application locale packs. Use `.description(...)` instead for
364
- short guidance that should remain visible below the field label.
365
-
366
- `ensureRakunInitialized()` prepares logger, MongoDB, media, and route syncing. It uses a singleton promise to avoid concurrent initialization; if initialization fails, the promise is cleared so the next call can retry.
367
-
368
- `ensureRakunBootstrap(options)` only calls `rakunBootstrap` if the runtime has not been bootstrapped yet.
369
-
370
- English is built into `@rakun-kit/manager-react`. Install only the extra
371
- manager UI locales an application needs:
372
-
373
- ```sh
374
- bun add @rakun-kit/manager-locales
375
- # or: npm install @rakun-kit/manager-locales
376
- ```
377
-
378
- Import the required language subpath and register it with `managerLanguages`;
379
- the public `manager.uiLocales` operation returns configured packs to the
380
- manager client. Its safe runtime configuration also includes the public SEO
381
- `siteUrl` and the configured home-page variant group, when available, so the
382
- manager can expose its Visit site action and classify SEO fallbacks correctly:
383
-
384
- ```ts
385
- import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
386
-
387
- rakunBootstrap({
388
- // ...
389
- managerLanguages: [esManagerLocalePack],
390
- })
391
- ```
392
-
393
- `managerLanguages` may also extend locales with arbitrary project keys. This is
394
- useful for translatable content-type titles and categories without adding host
395
- keys to the manager's static `ManagerMessageKey` union:
396
-
397
- ```ts
398
- import { extendManagerLanguagePack } from '@rakun-kit/core/contracts'
399
- import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
400
-
401
- rakunBootstrap({
402
- // ...
403
- managerLanguages: [
404
- {
405
- code: 'en',
406
- name: 'English',
407
- messages: {
408
- 'field.title': 'Title',
409
- 'layoutModule.header': 'Header',
410
- 'project.contentTypes.article.menu': 'Articles',
411
- },
412
- },
413
- extendManagerLanguagePack(esManagerLocalePack, {
414
- 'field.title': 'Título',
415
- 'layoutModule.header': 'Cabecera',
416
- 'project.contentTypes.article.menu': 'Artículos',
417
- }),
418
- ],
419
- })
420
- ```
421
-
422
- Content-type field labels automatically use `field.<fieldName>` from these
423
- project messages. Missing translations fall back to a built-in manager label
424
- when available and otherwise to the humanized field name.
425
-
426
- Route layout module labels use `layoutModule.<layoutKey>`, so a slot configured
427
- with `key: 'header'` resolves `layoutModule.header`.
428
-
429
- ## Plugins
430
-
431
- Trusted server plugins contribute to the same bootstrap registry without coupling
432
- core to React:
433
-
434
- ```ts
435
- import { defineRakunPlugin, rakunBootstrap } from '@rakun-kit/core'
436
-
437
- export const analyticsPlugin = defineRakunPlugin({
438
- id: '@acme/rakun-analytics',
439
- contentTypes: [AnalyticsEvent],
440
- routes: analyticsRoutes,
441
- apiOperations: analyticsOperations,
442
- permissions: ['plugin.analytics.view'],
443
- literals: {},
444
- initialize: async ({ db }) => {
445
- // Services and migrations are ready here. Keep initialization idempotent.
446
- },
447
- })
448
-
449
- rakunBootstrap({
450
- plugins: [analyticsPlugin],
451
- contentTypes: [],
452
- literals: {},
453
- mongo,
454
- })
455
- ```
456
-
457
- Plugin ids and contributed content types, routes, operations, literals, and
458
- custom field editor ids must be unique. Rakun reports both owners on conflicts.
459
- Visual manager and web facets are registered separately in their browser
460
- runtimes.
461
-
462
- Custom field factories can use `createPluginField`. Their serializable
463
- `meta.editor` must match a field declaration in the server plugin and a React
464
- editor registered by its manager facet. Each field also declares capabilities
465
- that shared systems consume instead of branching on its built-in type:
466
-
467
- ```ts
468
- const externalLink = createPluginField({
469
- meta: {
470
- type: 'Link',
471
- ui: 'Link',
472
- editor: '@acme/external-link',
473
- capabilities: {
474
- valueKind: 'object',
475
- dynamic: {
476
- properties: { title: 'string', href: 'string' },
477
- mapProperties: true,
478
- },
479
- },
480
- },
481
- schemas: sameSchemas(() => ExternalLinkSchema),
482
- runtime: {
483
- populate: (value, { populateLink }) => populateLink(value),
484
- },
485
- })
486
- ```
487
-
488
- `valueKind` controls Dynamic Data compatibility. `dynamic.properties` exposes
489
- nested source paths, `mapProperties` makes those paths independent mapping
490
- targets, `relation` enables content-type traversal, and `collection` describes
491
- `homogeneous` or `heterogeneous` per-item mapping. Capabilities are encoded for
492
- the manager. `runtime.populate` remains server-only and receives the database,
493
- recursive `populate`, and built-in `populateLink` helpers. The web pipeline
494
- exports this generic phase as `populateFields`; `populateLinks` remains a
495
- deprecated compatibility alias.
496
-
497
- ## Content Types
498
-
499
- A `ContentType` defines a logical collection:
500
-
501
- ```ts
502
- const Post = new ContentType({
503
- name: 'Post',
504
- menu: {
505
- title: 'Posts',
506
- icon: 'newspaper',
507
- category: 'Content',
508
- },
509
- fields: {
510
- title: f.string(),
511
- slug: f.string().type('Slug'),
512
- body: f.string().type('RichText').optional(),
513
- published: f.boolean().optional(),
514
- },
515
- uniques: [['slug']],
516
- listFields: ['title', 'slug', 'published'],
517
- })
518
- ```
519
-
520
- Page-like content types can define ordered page modules with `iterator` outside
521
- `fields`. Rakun persists this generated field as `_iterator`:
522
-
523
- ```ts
524
- import { StructuredData } from '@rakun-kit/core/internal-content-types'
525
-
526
- const Page = new ContentType({
527
- name: 'Page',
528
- fields: {
529
- title: f.string(),
530
- slug: f.string().type('Slug'),
531
- },
532
- iterator: [
533
- { contentType: PageSection, type: 'new' },
534
- { contentType: Hero, type: 'new' },
535
- { contentType: LayoutWithInfo, type: 'new' },
536
- { contentType: Newsletter, type: 'new' },
537
- { contentType: StructuredData, type: 'new' },
538
- ],
539
- })
540
- ```
541
-
542
- `StructuredData` is a built-in iterator module for JSON-LD. It provides typed
543
- Product, Article, Organization, WebSite, and BreadcrumbList forms plus a Custom
544
- JSON form. Add it to a shared Template and map its fields from the current
545
- document to reuse one schema across every page of the content type. The official
546
- React and Next renderers handle this module natively, so applications do not
547
- create a matching component file.
548
-
549
- `iterator` always belongs to the individual document and is edited in the
550
- manager's Content tab. When the content type has a configured `hasPage: true`
551
- route, Rakun automatically enables a separate shared Template tab with the
552
- same module picker as Content. The manager also offers one special Content slot
553
- at the template root and inside every `f.blocks(...)` field. The template must
554
- contain that slot exactly once; web output replaces it with the current
555
- document's iterator modules. This supports compositions such as a shared hero,
556
- a `LayoutWithInfo` wrapper containing the unique content, and a shared
557
- newsletter. Route layout modules such as header and footer still wrap the
558
- assembled result.
559
-
560
- Iterator and template modules can also be made conditional from the manager. A
561
- condition is stored on the module entry and evaluated against the current
562
- document:
563
-
564
- ```ts
565
- {
566
- name: "Credits",
567
- value: { /* module relation */ },
568
- visibleWhen: {
569
- field: "credits",
570
- operator: "notEmpty",
571
- },
572
- }
573
- ```
574
-
575
- Supported operators are `notEmpty` and `empty`. Conditional modules remain in
576
- their stored Content or Template composition but are omitted from web and
577
- preview output when their condition does not match.
578
-
579
- Main properties:
580
-
581
- - `name`: stable type name. Also used as `_type`.
582
- - `fields`: field map.
583
- - `iterator`: page module entries. Generates the reserved `_iterator` field.
584
- - `menu`: manager metadata.
585
- - `uniques`: unique field groups.
586
- - `listFields`: preferred fields in lists and relations.
587
- - `hideFromManager()`: hides the content type from manager content type lists.
588
- - `apiOnly()`: applies `.apiOnly()` to every field in the content type.
589
- - `managerOnly()`: applies `.managerOnly()` to every field in the content type.
590
- - Saved manager documents support comments and user mentions by default.
591
- - `withHooks()`: attaches lifecycle hooks such as `beforeInsert`, `beforeUpdate`, and `onGet`.
592
- - Dynamic data bindings are available on manager-visible fields by default; use
593
- field-level `.noDynamic()` to opt out.
594
- - A string field can initialize a new document's SEO dynamic data with
595
- `.seo('title')`, `.seo('description')`, or another string field from Rakun's
596
- built-in SEO model. This does not change existing documents.
597
-
598
- When a content type has a configured route with `hasPage: true`, Rakun adds an
599
- optional reserved `_seo` relation automatically. If that content type has an
600
- `iterator`, Rakun also enables its shared Template editor automatically.
601
-
602
- Hooks run around DB mutations and public output resolution:
603
-
604
- ```ts
605
- const User = new ContentType({
606
- name: 'User',
607
- fields: {
608
- email: f.string().type('Email').required(),
609
- password: f.string().type('Password').required().managerOnly(),
610
- },
611
- }).withHooks({
612
- beforeInsert: ({ data }) => ({
613
- ...data,
614
- password: hashPassword(String(data.password)),
615
- }),
616
- onGet: ({ data }) => ({
617
- ...data,
618
- displayName: String(data.email).split('@')[0],
619
- }),
620
- })
621
- ```
622
-
623
- Dynamic data turns a content type into a reusable layout. The manager can bind
624
- manager-visible fields to another content item field, or to a generated `href`
625
- when a source content type has a page route. Fields are dynamic by default; call
626
- `.noDynamic()` on fields that should not accept bindings or be exported as
627
- source paths. Source content types are hidden by default; set
628
- `dynamicDataSource: true` on content types that should appear in the manager
629
- source selector. Field bindings can also select `Current document` to read values
630
- from the document being edited without marking its content type as a source.
631
-
632
- Source field selectors are type-aware. A string target only offers string-like
633
- source paths, number targets only offer numbers, and boolean targets only offer
634
- booleans. Object-like source fields are traversed so nested leaf fields can be
635
- selected, while reserved SEO metadata is omitted from dynamic data mappings.
636
- The generated `href` source is only shown for content types that have a
637
- configured route with `hasPage: true`.
638
-
639
- In list mappings, a target `f.link()` is exposed as two independent target
640
- paths, `<field>.title` and `<field>.href`. Mapping those paths reconstructs the
641
- target link object instead of assigning a URL string to the complete field.
642
-
643
- Homogeneous `f.array(f.link())` and `f.array(f.relation(...))` targets can keep
644
- using a direct array field binding or switch to per-item list mapping.
645
- `f.relation(...).multiple()` is the same relation-array model and has the same
646
- mapping support. Link arrays expose `title` and `href` for every item; relation
647
- arrays expose the related content type's fields. Their resolved items stay flat,
648
- whereas heterogeneous `f.blocks(...)` items keep their `{ name, value }`
649
- wrapper.
650
-
651
- For example, a routeable category can pre-link its SEO title when a new category
652
- is created:
653
-
654
- ```ts
655
- const Category = new ContentType({
656
- name: 'Category',
657
- fields: {
658
- title: f.string().required().seo('title'),
659
- },
660
- })
661
- ```
662
-
663
- The manager stores this as a normal editable dynamic data binding from
664
- `seo.title` to `Current document · title`.
665
-
666
- ```ts
667
- const Project = new ContentType({
668
- name: 'Project',
669
- dynamicDataSource: true,
670
- fields: {
671
- title: f.string().required(),
672
- slug: f.string().type('Slug').required(),
673
- },
674
- })
675
-
676
- const Carousel = new ContentType({
677
- name: 'Carousel',
678
- fields: {
679
- title: f.string().required(),
680
- internalNote: f.string().noDynamic(),
681
- items: f.blocks([
682
- {
683
- name: 'CarouselItem',
684
- field: f.relation(CarouselItem, 'new'),
685
- },
686
- ]),
687
- },
688
- })
689
- ```
690
-
691
- List bindings append dynamic items to manually stored items instead of replacing
692
- the list. If the same stable item id appears in both sources, the dynamically
693
- resolved item wins and the duplicate manual copy is skipped.
694
-
695
- A list inside a nested module can also use a compatible relation or blocks array
696
- from the current document. The manager exposes these sources as
697
- `Current document · <field>`, infers the content type of each array item, and
698
- lets each item be mapped with the same field mapping UI used for collections.
699
- These bindings store a `currentDocument` source alongside the inferred item
700
- content type:
701
-
702
- ```ts
703
- const headerBindings = {
704
- lists: {
705
- categories: {
706
- contentType: LinkItem.name,
707
- source: {
708
- kind: 'currentDocument',
709
- contentType: Project.name,
710
- path: 'categories',
711
- itemName: 'Category',
712
- },
713
- itemName: 'Category',
714
- map: {
715
- title: { contentType: LinkItem.name, path: 'title' },
716
- href: { contentType: LinkItem.name, path: 'href' },
717
- },
718
- },
719
- },
720
- }
721
- ```
722
-
723
- List query conditions can compare a source field with a value from the current
724
- context. At the top level this is the current document; inside a nested list it
725
- is the parent source item and the manager labels it `Current item`. Programmatic
726
- bindings use `{ $current: "path.to.field" }`. For example, a category can query
727
- only the projects whose related category has the same slug:
728
-
729
- ```ts
730
- query: {
731
- filter: {
732
- "category.slug": { $current: "slug" },
733
- },
734
- options: { limit: 10 },
735
- }
736
- ```
737
-
738
- Nested conditions can still read the root document by selecting `Current
739
- document`, which stores `{ $document: "path.to.field" }`. Current-item and
740
- current-document paths are checked against their respective content type's
741
- dynamic field rules before the query runs. `_id` is also available for relation
742
- queries.
743
-
744
- List mappings can be nested recursively when a mapped target field is itself a
745
- `blocks` list, link array, or relation array. Use `kind: "list"` for that map
746
- entry and configure its source, query, item type, and field map exactly like a
747
- top-level list binding. Inside the nested query, `$current` refers to the source
748
- item being mapped by the parent list:
749
-
750
- ```ts
751
- map: {
752
- title: { contentType: Category.name, path: "title" },
753
- images: {
754
- kind: "list",
755
- contentType: Project.name,
756
- itemName: "CategoriesGalleryItemImage",
757
- query: {
758
- filter: { "category._id": { $current: "_id" } },
759
- options: { limit: 10, sort: { title: "asc" } },
760
- },
761
- map: {
762
- title: { contentType: Project.name, path: "title" },
763
- href: { contentType: Project.name, virtual: "href" },
764
- image: { contentType: Project.name, path: "image" },
765
- },
766
- },
767
- }
768
- ```
769
-
770
- The manager exposes this as the `Nested list` mapping mode. Nested mappings
771
- apply the same source opt-in, field compatibility, query validation, and
772
- `.noDynamic()` rules at every level.
773
-
774
- A list mapping can also collect an array through a reverse relation without
775
- persisting that relation on the source document. For example, a category gallery
776
- can create one item per `Category` and collect the images of its related
777
- `Project` documents:
778
-
779
- ```ts
780
- const Category = new ContentType({
781
- name: 'Category',
782
- dynamicDataSource: true,
783
- fields: {
784
- title: f.string().required(),
785
- },
786
- })
787
-
788
- const Project = new ContentType({
789
- name: 'Project',
790
- dynamicDataSource: true,
791
- fields: {
792
- category: f.relation(Category, 'existing').required(),
793
- images: f.file().type('Image').multiple().required(),
794
- },
795
- })
796
-
797
- const galleryBindings = {
798
- lists: {
799
- items: {
800
- contentType: Category.name,
801
- itemName: 'CategoriesGalleryItem',
802
- map: {
803
- title: { contentType: Category.name, path: 'title' },
804
- images: {
805
- kind: 'relatedCollection',
806
- contentType: Project.name,
807
- relation: 'category',
808
- path: 'images',
809
- limit: 10,
810
- },
811
- },
812
- },
813
- },
814
- }
815
- ```
816
-
817
- The related collection query matches `Project.category._id` against the current
818
- category, preserves project and image order, and flattens the selected array by
819
- one level. Its numeric limit applies to related projects and is capped at 100.
820
-
821
- Schema and validation methods:
822
-
823
- - `getInputSchema()`: write schema. Includes `_type`, `createdBy`, and `updatedBy`.
824
- - `getSchema()`: persisted schema.
825
- - `getPopulatedSchema()`: output with populated relations and `_id`.
826
- - `getOutputSchema()`: public API output.
827
- - `getOutputSchemaWithoutIterators()`: output variant without iterator fields.
828
- - `validate`, `partialValidate`, `validateOutput`: Zod helpers.
829
-
830
- The registry lives in `lib/Registry`:
831
-
832
- - `registerContentType`, `registerInternalContentType`.
833
- - `getContentTypes`, `getExternalContentTypes`, `getInternalContentTypes`.
834
- - `getContentTypesForManager`: returns serializable content types without Zod schemas and without content types marked with `hideFromManager()`.
835
- - `getContentTypeByName`.
836
-
837
- ## Fields
838
-
839
- Use the concise `f` namespace for field factories. `Fields` remains exported as
840
- a backward-compatible alias and references the same object.
841
-
842
- Main factories:
843
-
844
- ```ts
845
- f.string()
846
- f.number()
847
- f.boolean()
848
- f.date()
849
- f.select(['draft', 'published'])
850
- f.relation(Post)
851
- f.contentReference('Post')
852
- f.selfRelation()
853
- f.blocks([{ name: 'title', field: f.string() }])
854
- f.array(f.string())
855
- f.link()
856
- f.menu()
857
- f.file()
858
- f.breadcrums()
859
- ```
860
-
861
- Common modifiers:
862
-
863
- - Fields are required by default. Use `.optional()` when a value may be omitted.
864
- - `.required()` explicitly marks a field as required. It remains available for
865
- clarity and backward compatibility.
866
- - `.translatable()`: stores values per language with shape `{ _tag: "Translatable", ... }`.
867
- - `.apiOnly()`: available for API/persistence, hidden from manager.
868
- - `.managerOnly()`: visible to manager, excluded from API output.
869
-
870
- Notable fields:
871
-
872
- - `StringField`: UI `Text`, `Textarea`, `RichText`, `Email`, `Slug`, `Password`, `Id`, `Url`; supports `.min()` and `.max()`.
873
- - `NumberField`: supports `.min()` and `.max()`.
874
- - `DateField`: supports `.type("Date")`, `.type("DateTime")` and
875
- `.type("Time")`. Date and datetime writes accept JavaScript `Date` values or
876
- their ISO datetime JSON representation and normalize them to `Date`; time
877
- values remain ISO time strings.
878
- - `RelationField`: relation to another `ContentType`; accepts existing references or inline creation. `f.relation(Post, "existing")` restricts to existing records; `"new"` restricts to new records. `.multiple()` returns a homogeneous array of relations.
879
- - `ContentReferenceField`: reference by content type name.
880
- - `LinkField`: the manager stores a direct `{ href, title }` value or an
881
- internal `{ routeId, contentTypeId, title }` reference. Titled links resolve
882
- to `{ href, title }` in web output, with internal `href` values localized by
883
- route. New input and web output always use an object shape. Persisted legacy
884
- direct URL strings remain readable and receive an empty `title` when loaded
885
- by the manager or normalized for web output.
886
- - `MenuField`: `f.menu()` stores an ordered navigation tree. Every node is an
887
- internal or direct link plus `children`, for example
888
- `{ title, href, children: [...] }`. Internal references use `routeId` and
889
- `contentTypeId` while persisted and resolve recursively to localized `href`
890
- values in web output. The manager supports drag-and-drop ordering and
891
- unlimited nested levels.
892
- - `BreadcrumsField`: `f.breadcrums()` is a computed, API-only field for page
893
- modules. In web and preview output it returns the localized route hierarchy as
894
- `{ label, href }[]`, ordered from the highest ancestor to the current page. It
895
- returns `null` when the content is resolved outside a routable page and is
896
- never accepted as input, persisted, or rendered in the manager.
897
- - `FileField`: integrates media and optimization options.
898
- - `f.blocks(...)`: heterogeneous ordered list. Each item stores a `name` and a `value`, and the value can match one of the named field shapes. Use it for block-like content where different item types can appear in the same list.
899
- - `f.array(...)`: homogeneous ordered list. Every item uses the same field shape. Chain `.min(count)` and `.max(count)` to validate its item count. The same limits are available after `.multiple()` on relation, file, select, and content-reference fields, for example `f.file().multiple().min(1).max(4)`.
900
- - `IteratorField`: repeatable structure based on content type entries.
901
-
902
- ## Derived Types
903
-
904
- `@rakun-kit/core/types` and `lib/types` derive types from `ContentType`:
905
-
906
- - `DataInput<T>`: accepted shape for create/update.
907
- - `DBOutput<T>`: persisted shape with metadata (`_id`, `_type`, timestamps, authorship).
908
- - `DataPopulated<T>`: shape with populated relations.
909
- - `DataFront<T>`: public output without `apiOnly` fields.
910
- - `Filter<T>`, `Query<T>`, `ListInput<T>`, `GetAllInput<T>`: typed filters and list inputs.
911
- - `MaybeTranslatableValue<T>`, `TranslatableValue<T>`.
912
-
913
- ## ORM and Mongo
914
-
915
- `core/src/orm` implements `DBService` on top of MongoDB:
916
-
917
- ```ts
918
- const db = await getMongoService()
919
-
920
- const post = await db.create(Post, {
921
- _type: 'Post',
922
- title: 'Hello',
923
- slug: 'hello',
924
- })
925
- ```
926
-
927
- Operations:
928
-
929
- - `get(contentType, id, fields?)`
930
- - `list(contentType, query)`
931
- - `create(contentType, data)`
932
- - `update(contentType, id, data)`
933
- - `updateMany(contentType, filter, data)`
934
- - `delete(contentType, filter)`
935
- - `find(contentType, filter, fields?)`
936
- - `clear(contentType)`
937
- - `findDependencies(contentType, id)`
938
- - `upsert(contentType, filter, data)`
939
- - `getAll(contentType, query?)`
940
-
941
- Connection:
942
-
943
- - `createMongoConnection(config)`: stores config.
944
- - `createMongoService(config)`: connects and creates handlers.
945
- - `getMongoService()`: returns the singleton or creates it from config.
946
- - `closeMongoService()`: closes the connection and clears the singleton.
947
-
948
- `MongoConfig`:
949
-
950
- ```ts
951
- type MongoConfig = {
952
- MONGO_URI: string
953
- ENVIRONMENT?: 'local' | 'development' | 'test' | 'production'
954
- }
955
- ```
956
-
957
- In environments other than `test`, the connection creates indexes defined by `createIndexes`.
958
-
959
- ## API Operations
960
-
961
- `api/operations` defines typed contracts and handlers for endpoints:
962
-
963
- - Manager: CRUD, auth, MFA, media, literals, settings.
964
- - Manager comments and mentions: `manager.comments.list`,
965
- `manager.comments.create`, `manager.comments.toggleReaction`,
966
- `manager.comments.markRead`, `manager.comments.unreadCount`,
967
- `manager.users.mentions`,
968
- `manager.notifications.list`, and `manager.notifications.markRead`.
969
- - Web: page resolution, static paths, sitemap, robots, `llms.txt`, and preview.
970
-
971
- `web.staticPaths` returns `{ path, ttl }` only for route-map entries backed by
972
- page routes configured with `dynamic: false`. Adapters can use this operation
973
- for static generation without querying Rakun's database directly. Monolithic
974
- server adapters can instead call `getRakunWebStaticPaths`, `getRakunWebPage`,
975
- and `getRakunWebPreviewPage` after bootstrapping and initializing core.
976
-
977
- `web.llms` returns the optional site-level `llms.txt` document configured in
978
- Manager Settings. Rakun renders a curated Markdown title, summary, guidance,
979
- sections, and links; it does not dump the sitemap automatically. Internal links
980
- are resolved for the requested language and become absolute when SEO `siteUrl`
981
- is configured. Links without a public route are omitted without hiding their
982
- section heading. Optional sections retain their titles beneath the conventional
983
- `## Optional` heading. The llms title and summary fall back to SEO `siteName` and the
984
- default SEO description. The result is `null` while publishing is disabled or
985
- there is no usable title. Server adapters can call `getRakunWebLlmsTxt`
986
- directly after initialization.
987
-
988
- `manager.localeVariants.list` includes an optional `path` on each language
989
- assignment when that exact assigned document is published and has a generated
990
- page route. Manager clients can use it for a contextual public-page link without
991
- granting editors direct access to internal `RouteMap` records.
992
-
993
- Routeable content with document visibility is public only when `_visibility`
994
- is explicitly `published` or `hidden`. Missing visibility is treated as draft,
995
- both while generating route maps and when resolving a public page, so legacy or
996
- incomplete records fail closed even if an obsolete route-map entry remains.
997
-
998
- Main helpers:
999
-
1000
- - `defineOperationContract`: declares a contract with Zod input/output, method, path, and metadata.
1001
- - `defineOperation`: combines a contract with its implementation.
1002
- - `createRakunApiClient`: creates a browser/server HTTP client for custom operations.
1003
- - `GetClient`: derives a typed client from an operation map.
1004
- - `createManagerOperationContracts`, `createWebOperationContracts`.
1005
- - `createManagerOperationDefinitions`, `createWebOperationDefinitions`.
1006
- - `createRakunOperationDefinitions`: combines manager + web.
1007
- - `createOperationManifest`: serializable metadata for clients.
1008
- - `createOperationPath`: HTTP path from operation name.
1009
- - `mergeOperationContracts`: combines maps.
1010
-
1011
- `@rakun-kit/core/manager` exposes the manifest and name-based types:
1012
-
1013
- ```ts
1014
- import {
1015
- managerOperationManifest,
1016
- getManagerOperationMeta,
1017
- type ManagerOperationInput,
1018
- type ManagerOperationOutput,
1019
- } from '@rakun-kit/core/manager'
1020
- ```
1021
-
1022
- ### Custom Operations
1023
-
1024
- Applications can define operations in a separate object, pass that object to
1025
- bootstrap, and reuse its type on the frontend:
1026
-
1027
- ```ts
1028
- // server/api-operations.ts
1029
- import { defineOperation } from '@rakun-kit/core'
1030
- import { z } from 'zod'
1031
-
1032
- export const apiOperations = {
1033
- 'demo.helloWorld': defineOperation<
1034
- { text: string },
1035
- { message: string },
1036
- 'query',
1037
- 'get',
1038
- 'public'
1039
- >({
1040
- access: 'public',
1041
- kind: 'query',
1042
- method: 'get',
1043
- description: 'Return a hello world message with the provided text',
1044
- input: z.object({
1045
- text: z.string().default('world'),
1046
- }),
1047
- output: z.object({
1048
- message: z.string(),
1049
- }),
1050
- resolve: ({ input }) => ({
1051
- message: `Hello ${input.text}`,
1052
- }),
1053
- }),
1054
- }
1055
- ```
1056
-
1057
- ```ts
1058
- // bootstrap
1059
- import { apiOperations } from './server/api-operations'
1060
-
1061
- rakunBootstrap({
1062
- // ...
1063
- apiOperations,
1064
- })
1065
- ```
1066
-
1067
- Operation names define their HTTP path: `demo.helloWorld` becomes
1068
- `/demo/helloWorld`.
1069
-
1070
- Rules:
1071
-
1072
- - Names must not collide with built-in operations.
1073
- - Operations prefixed with `manager.` are included in manager operation routers.
1074
- - Operations prefixed with `web.` are included in web operation routers.
1075
- - Operations with any other prefix are included when using the combined operation registry.
1076
- - Use `access: "public"` for operations called from public web pages.
1077
- - Use `access: "auth"` for operations that require a manager session.
1078
-
1079
- The manager route `manager.apiOperations` returns a JSON-serializable operation
1080
- catalog for manager API docs/playgrounds. Input and output Zod schemas are
1081
- converted to JSON Schema for display.
1082
-
1083
- ### Typed API Client
1084
-
1085
- `@rakun-kit/core/web` exposes a small typed HTTP client for operation maps:
1086
-
1087
- ```ts
1088
- import { createRakunApiClient, type GetClient } from '@rakun-kit/core/web'
1089
- import type { apiOperations } from './server/api-operations'
1090
-
1091
- type ApiClient = GetClient<typeof apiOperations>
1092
-
1093
- const client: ApiClient = createRakunApiClient<typeof apiOperations>({
1094
- baseUrl: '/api',
1095
- })
1096
-
1097
- const result = await client.query('demo.helloWorld', {
1098
- text: 'Rakun',
1099
- })
1100
-
1101
- result.message
1102
- ```
1103
-
1104
- The client exposes:
1105
-
1106
- - `query(name, input?, options?)`: only accepts operations with `kind: "query"`.
1107
- - `mutation(name, input?, options?)`: only accepts operations with `kind: "mutation"`.
1108
-
1109
- The client derives input and output types from the Zod schemas in
1110
- `apiOperations`.
1111
-
1112
- ## Request Context and Auth
1113
-
1114
- `createRequestContext(input)` normalizes headers, cookies, and response:
1115
-
1116
- ```ts
1117
- const ctx = await createRequestContext({
1118
- headers: req.headers,
1119
- cookies,
1120
- res: {
1121
- setHeader: res.setHeader.bind(res),
1122
- cookie: res.cookie.bind(res),
1123
- },
1124
- })
1125
- ```
1126
-
1127
- The resulting context includes:
1128
-
1129
- - `req.headers`, `req.cookies`
1130
- - `res.setHeader`, `res.cookie`
1131
- - `user`: manager user or `null`
1132
- - `getUser()`: returns the user or throws `AUTH_REQUIRED`
1133
-
1134
- Session cookies are managed with `getSessionCookie` and `setSessionCookie`.
1135
-
1136
- ## Media
1137
-
1138
- Media uses a storage adapter:
1139
-
1140
- ```ts
1141
- createMediaService({
1142
- adapter,
1143
- defaultAccess: 'private',
1144
- defaultGetExpiresInSeconds: 300,
1145
- uploadUrl: '/api/rakun/manager/media/upload',
1146
- })
1147
- ```
1148
-
1149
- APIs:
1150
-
1151
- - `createMediaConnection(config)`: stores config without creating the service.
1152
- - `createMediaService(config)`: creates the singleton with an adapter.
1153
- - `getMediaService()`: returns the singleton or creates it from config.
1154
- - `handleMediaBinaryUpload`: processes manager binary uploads.
1155
-
1156
- The service supports prepare/finalize upload, URL generation, folders, and media optimization depending on adapter/configuration. Manager uploads encode original file names for transport, so Unicode names (including accents, non-Latin scripts, and emoji) are preserved without placing invalid characters in HTTP headers.
1157
-
1158
- Adapters can implement `getPublicObject` to let every Rakun server adapter proxy
1
+ # @rakun-kit/core
2
+
3
+ `@rakun-kit/core` contains Rakun's shared domain model and runtime. It defines content types, fields, Zod schemas, manager/web operations, request context, MongoDB connection handling, media service, permissions, translations, and typed contracts used by adapters (`@rakun-kit/next`, `@rakun-kit/express`, `@rakun-kit/trpc`) and the manager client.
4
+
5
+ ## Main Entry
6
+
7
+ Typical imports:
8
+
9
+ ```ts
10
+ import { ContentType, f, rakunBootstrap, ensureRakunInitialized } from '@rakun-kit/core'
11
+ ```
12
+
13
+ The main entrypoint exports:
14
+
15
+ - Bootstrap: `rakunBootstrap`, `ensureRakunBootstrap`, `ensureRakunInitialized`, `getRakunBootstrapOptions`.
16
+ - Context: `createRequestContext`, `getSessionCookie`, `setSessionCookie`.
17
+ - Operations: manager/web contracts and definitions.
18
+ - Media: `createMediaService`, `getMediaService`, adapters, and types.
19
+ - Mail: `createMailService`, `sendMail`, typed template registries, adapters, and types.
20
+ - Permissions, translations, errors, contracts, and public types.
21
+ - Basic internal content types: `Language`, `ManagerUser`, `Seo`.
22
+
23
+ Relevant subpaths:
24
+
25
+ - `@rakun-kit/core/manager`: manager operation metadata and types.
26
+ - `@rakun-kit/core/contracts`: shared manager/web payload schemas.
27
+ - `@rakun-kit/core/client`: client-facing types and utilities.
28
+ - `@rakun-kit/core/logger`: logger.
29
+ - `@rakun-kit/core/errors`: application errors.
30
+ - `@rakun-kit/core/types`: types derived from content types.
31
+
32
+ ## Bootstrap
33
+
34
+ `rakunBootstrap(options)` registers global configuration and content types:
35
+
36
+ ```ts
37
+ rakunBootstrap({
38
+ literals,
39
+ contentTypes: [Page, Post],
40
+ routes,
41
+ apiOperations,
42
+ mongo: {
43
+ MONGO_URI: process.env.MONGO_URI!,
44
+ ENVIRONMENT: process.env.NODE_ENV === 'test' ? 'test' : 'production',
45
+ },
46
+ media,
47
+ logger: {
48
+ level: 'info',
49
+ prettify: true,
50
+ },
51
+ syncRoutes: true,
52
+ })
53
+ ```
54
+
55
+ Options:
56
+
57
+ - `literals`: text/translation catalog.
58
+ - `contentTypes`: application content types.
59
+ - `internalContentTypes`: optional overrides for Rakun internal content types, currently `Page`.
60
+ - `routes`: configured routes used to resolve pages.
61
+ - `apiOperations`: custom API operations added to the Rakun operation registry.
62
+ - `mongo`: MongoDB connection. Required before serving Rakun requests.
63
+ - `media`: media adapter/configuration. Optional.
64
+ - `mail`: outbound mail adapter and default sender configuration. Optional.
65
+ - `collaboration`: storage adapter for unsaved Yjs content working documents.
66
+ Optional; defaults to process memory.
67
+ - `accountRecovery`: password-reset URL builder, expiry, and optional custom
68
+ mail template. Requires `mail`; core provides the default template.
69
+ - `login`: manager password-login toggle and external login adapters.
70
+ - `logger`: logger configuration. If omitted, an `info` logger with `prettify` is created.
71
+ - `syncRoutes`: syncs configured routes during initialization. Enabled by default.
72
+
73
+ ## Runtime Platform
74
+
75
+ `rakunBootstrap` accepts an optional resolved `platform`. Without one, Rakun
76
+ detects Bun through `process.versions.bun` and otherwise uses Node.js defaults.
77
+ Runtime, framework, and deployment are separate values, and every capability
78
+ can be overridden without configuring the rest:
79
+
80
+ ```ts
81
+ import { createPlatform, pollingRealtime, sharpImage } from '@rakun-kit/core'
82
+
83
+ rakunBootstrap({
84
+ ...options,
85
+ platform: createPlatform({
86
+ deployment: 'serverless',
87
+ image: sharpImage(),
88
+ realtime: pollingRealtime({ intervalMs: 5_000 }),
89
+ }),
90
+ })
91
+ ```
92
+
93
+ The default image processor is native `Bun.Image` when available and `sharp`
94
+ otherwise. Node.js image optimization therefore requires the optional `sharp`
95
+ peer. Bun also falls back to it when an OS-dependent codec cannot handle the
96
+ requested format. Custom `ImageProcessor` implementations provide
97
+ `metadata`, `transform`, and `placeholder`; the latter returns an inline
98
+ `{ dataUrl, mime }` LQIP. Bun uses native `Bun.Image.placeholder()`, while
99
+ Sharp produces the placeholder through its normal reduced transform and serves
100
+ as the fallback when the Bun method is unavailable. The
101
+ platform resolves crypto, filesystem, compression,
102
+ workers, and realtime capabilities. Polling is the safe realtime default when
103
+ the host cannot keep persistent connections open. The Next and Express
104
+ adapters automatically serve configured SSE streams.
105
+ Calling `sseRealtime()` uses the API-relative `/realtime` endpoint; pass an
106
+ explicit `endpoint` only when the public route differs.
107
+
108
+ The manager bootstrap exposes only the safe transport metadata from this
109
+ provider. Manager mutations publish invalidation topics for collaborative
110
+ documents, locale variants, versions, comments, and notifications. Event
111
+ transports therefore refresh matching queries on demand; polling keeps the
112
+ same behavior through its configured interval.
113
+
114
+ Server adapters share `parseRealtimeTopics`, `isRealtimeEndpointRequest`,
115
+ `createRealtimeSseStream`, and `authorizeRealtimeSubscription` from core. A
116
+ custom HTTP framework can use these primitives without importing either the
117
+ Next or Express adapter.
118
+
119
+ The built-in SSE provider uses an in-process topic broker. For multiple server
120
+ processes or replicas, provide a `RealtimeProvider` backed by a shared broker
121
+ while keeping the same transport metadata and subscription contract.
122
+
123
+ ## Collaborative content documents
124
+
125
+ Every saved content document and draft has an independent Yjs working document
126
+ keyed by its content type and `_id`. Editor updates synchronize through the
127
+ authenticated `manager.contentCollaboration.sync` operation without changing
128
+ the MongoDB content snapshot or triggering revalidation. Save calls
129
+ `manager.contentCollaboration.save`, which materializes and validates the
130
+ server-side CRDT state before using the normal update and revalidation flow.
131
+ Draft promotion remains separate and promotes only a saved draft snapshot.
132
+ `manager.contentCollaboration.discard` restores the room from that persisted
133
+ snapshot, publishes the reset to connected editors, and marks the restored Yjs
134
+ state as saved without creating a new content revision. Shared templates expose
135
+ the equivalent `manager.templateCollaboration.discard` operation.
136
+
137
+ The default adapter keeps working documents in process memory. Configure a
138
+ shared, durable implementation for replicated or restart-durable deployments:
139
+
140
+ ```ts
141
+ rakunBootstrap({
142
+ // ...
143
+ collaboration: {
144
+ adapter: {
145
+ load: async (roomId) => loadRoom(roomId),
146
+ save: async (roomId, state) => saveRoom(roomId, state),
147
+ loadPresence: async (roomId) => loadRoomPresence(roomId),
148
+ savePresence: async (roomId, presence) => saveRoomPresence(roomId, presence),
149
+ delete: async (roomId) => deleteRoom(roomId),
150
+ dispose: () => closeRoomStore(),
151
+ },
152
+ },
153
+ })
154
+ ```
155
+
156
+ Core releases a hydrated `Y.Doc` after five minutes without access and reloads
157
+ it from the adapter on demand. Set `collaboration.roomIdleTimeoutMs` to tune
158
+ that memory cache, or `0` to disable idle eviction. This does not delete adapter
159
+ state or unsaved changes. Optional synchronous `adapter.dispose()` runs when
160
+ the collaboration service is replaced or core shuts down.
161
+
162
+ `state.update` and `state.savedStateVector` are opaque `Uint8Array` values.
163
+ Store them outside the public content record. The built-in manager applies this
164
+ to saved content edit screens and to each content type's shared Template.
165
+ Templates use their own `template:<contentType>` room rather than an individual
166
+ document room. Settings, route-layout overrides, and other administrative forms
167
+ remain local. Translation changes join the working document and are persisted
168
+ only by Save.
169
+
170
+ The sync response also includes ephemeral viewers keyed by browser-tab client
171
+ id, including the focused field. Presence expires automatically and is not
172
+ part of `state.update`, `state.savedStateVector`, or the persistent event log.
173
+ With SSE, the authenticated subscription owns the tab presence: server
174
+ heartbeats renew it and closing the last stream for that tab removes it, so the
175
+ manager does not send a periodic collaboration sync request just to stay
176
+ visible. Polling refreshes presence through its normal sync cycle.
177
+ `loadPresence` and `savePresence` are optional; implement both in replicated
178
+ deployments that need presence shared across processes.
179
+
180
+ `@rakun-kit/manager-react` additionally stores these Yjs rooms per user in
181
+ IndexedDB, allowing cached content and Template fields to remain editable during
182
+ temporary connection loss. Reconnection exchanges the local updates with core;
183
+ only an online Save commits them to the public content snapshot.
184
+
185
+ ## External manager login
186
+
187
+ Configure external login methods in `rakunBootstrap`. The callback URL is the
188
+ manager's public `/login/callback` route.
189
+
190
+ ```ts
191
+ import {
192
+ createGitHubLoginAdapter,
193
+ createGoogleLoginAdapter,
194
+ createMicrosoftLoginAdapter,
195
+ rakunBootstrap,
196
+ } from '@rakun-kit/core'
197
+
198
+ const callbackUrl = 'https://cms.example.com/backend/login/callback'
199
+
200
+ rakunBootstrap({
201
+ // ...
202
+ login: {
203
+ password: true,
204
+ adapters: [
205
+ createGitHubLoginAdapter({
206
+ clientId: process.env.GITHUB_CLIENT_ID!,
207
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
208
+ redirectUri: callbackUrl,
209
+ }),
210
+ createGoogleLoginAdapter({
211
+ clientId: process.env.GOOGLE_CLIENT_ID!,
212
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
213
+ redirectUri: callbackUrl,
214
+ }),
215
+ createMicrosoftLoginAdapter({
216
+ clientId: process.env.MICROSOFT_CLIENT_ID!,
217
+ clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
218
+ redirectUri: callbackUrl,
219
+ tenant: 'common',
220
+ }),
221
+ ],
222
+ },
223
+ })
224
+ ```
225
+
226
+ Register the exact callback URL in each provider console. External identities
227
+ must return a verified email matching an
228
+ existing manager user. Rakun does not create manager accounts automatically.
229
+ The flow uses an HttpOnly state cookie and PKCE and preserves the existing MFA
230
+ step. Set `password: false` only when at least one adapter is configured.
231
+ Custom providers can be implemented with `defineLoginAdapter`.
232
+
233
+ ### Persistent password-login IP blocking
234
+
235
+ Password login enables persistent fail2ban-style IP blocking by default. Five
236
+ failed attempts block the IP indefinitely; a successful login before reaching
237
+ the limit clears its failed-attempt counter. Configure or disable it through
238
+ `login.fail2ban`:
239
+
240
+ ```ts
241
+ rakunBootstrap({
242
+ // ...
243
+ login: {
244
+ password: true,
245
+ fail2ban: {
246
+ maxAttempts: 5,
247
+ // Optional when the deployment uses a custom trusted proxy:
248
+ // resolveIp: (ctx) => ctx.req?.headers?.['your-trusted-ip-header'] as string,
249
+ },
250
+ },
251
+ })
252
+ ```
253
+
254
+ Users with the `auth.ipBlocks.manage` permission can inspect and remove blocks
255
+ under Manager Settings → Security. Admin roles receive this built-in permission
256
+ automatically. A block can also be removed directly from MongoDB by deleting
257
+ the matching document from the `LoginIpBlock` collection. Set `fail2ban: false`
258
+ to disable this behavior. External login adapters are not affected.
259
+
260
+ ## Account recovery and MFA recovery
261
+
262
+ Password recovery is enabled when both `mail` and `accountRecovery` are
263
+ configured. The reset URL must point to the manager's public
264
+ `/reset-password` screen and preserve the token as the `token` query parameter:
265
+
266
+ ```ts
267
+ import { rakunBootstrap } from '@rakun-kit/core'
268
+ import { createResendMailServiceConfig } from '@rakun-kit/resend'
269
+
270
+ rakunBootstrap({
271
+ // ...
272
+ mail: createResendMailServiceConfig({
273
+ apiKey: process.env.RESEND_API_KEY!,
274
+ defaultFrom: process.env.RAKUN_MAIL_FROM!,
275
+ }),
276
+ accountRecovery: {
277
+ passwordReset: {
278
+ expiresInMs: 60 * 60 * 1000,
279
+ createUrl: (token) =>
280
+ `https://cms.example.com/backend/reset-password?token=${encodeURIComponent(token)}`,
281
+ },
282
+ },
283
+ })
284
+ ```
285
+
286
+ Core uses its branded HTML and plain-text password-reset template by default.
287
+ Pass `accountRecovery.passwordReset.template` only when the application needs
288
+ to replace it with custom branding or copy.
289
+
290
+ Reset tokens are random, stored only as SHA-256 hashes, expire, and are consumed
291
+ once. A successful reset closes every existing session but never disables MFA.
292
+ The request endpoint always returns the same result for known and unknown email
293
+ addresses.
294
+
295
+ Enabling TOTP or the first WebAuthn device returns one-time recovery codes to
296
+ the manager UI. Only their hashes are persisted, each code is consumed after
297
+ one login, and users can replace the entire set from their account after
298
+ confirming their current password. MFA state cannot be disabled through generic
299
+ manager content operations. If a user loses all MFA methods and recovery codes,
300
+ recovery must be handled by an authorized person through a channel outside the
301
+ CMS.
302
+
303
+ Content types can define lifecycle `hooks` and opt into manager-selected
304
+ `dynamicData` sources.
305
+
306
+ Routes can define fixed layout module slots. Rakun syncs those slots and the
307
+ manager lets users select an existing entry for each route:
308
+
309
+ ```ts
310
+ rakunBootstrap({
311
+ // ...
312
+ routes: [
313
+ {
314
+ key: 'pages',
315
+ contentType: 'Page',
316
+ field: 'slug',
317
+ hasPage: true,
318
+ dynamic: false,
319
+ defaultBasePath: '',
320
+ layout: [
321
+ { key: 'header', contentType: 'Header' },
322
+ { type: 'content' },
323
+ { key: 'footer', contentType: 'Footer' },
324
+ ],
325
+ },
326
+ ],
327
+ })
328
+ ```
329
+
330
+ The web page response includes an ordered `layout` array containing module slots
331
+ plus the content slot. This is the only module collection in the response:
332
+ content modules live in the `modules` property of the `content` layout item and
333
+ are not duplicated at the top level.
334
+
335
+ The response `info` object contains the page's normal resolved fields plus
336
+ route context such as `locale` and `variantGroupId`. Translated website literals
337
+ live in the separate top-level `literals` property. Composition-only fields
338
+ (`_iterator`, `_seo`, and the legacy `_iteratorUnlinked` marker) are removed
339
+ recursively from the page and all populated relations before `info` is sent.
340
+
341
+ ## Module picker
342
+
343
+ Content types used as iterator modules can customize their card in the manager's
344
+ add-module dialog. `modulePicker.preview` accepts an image URL available to the
345
+ manager browser; relative URLs are also supported:
346
+
347
+ ```ts
348
+ const Hero = new ContentType({
349
+ name: 'Hero',
350
+ modulePicker: {
351
+ title: 'Hero section',
352
+ description: 'Large introduction with heading, copy, and CTA.',
353
+ category: 'Marketing',
354
+ icon: 'PanelTop',
355
+ preview: '/images/modules/hero.webp',
356
+ keywords: ['banner', 'cover'],
357
+ },
358
+ fields: {
359
+ title: f.string().help('content.heroTitleHelp').required(),
360
+ },
361
+ })
362
+ ```
363
+
364
+ When `preview` is omitted or cannot be loaded, the picker displays a neutral
365
+ placeholder so module cards keep a consistent height.
366
+
367
+ Use field `.help(text)` for longer, optional guidance. The manager places a help
368
+ icon beside that field and opens the text on demand, including inside iterator
369
+ and blocks modules. `text` may be direct copy or a manager translation key
370
+ supplied by the application locale packs. Use `.description(...)` instead for
371
+ short guidance that should remain visible below the field label.
372
+
373
+ `ensureRakunInitialized()` prepares logger, MongoDB, media, and route syncing. It uses a singleton promise to avoid concurrent initialization; if initialization fails, the promise and any partially opened MongoDB client are cleared so the next call can retry. Await `shutdownRakun()` when the host stops to close all MongoDB clients, release hydrated collaboration documents, and clear transient authentication state.
374
+
375
+ `ensureRakunBootstrap(options)` only calls `rakunBootstrap` if the runtime has not been bootstrapped yet.
376
+
377
+ English is built into `@rakun-kit/manager-react`. Install only the extra
378
+ manager UI locales an application needs:
379
+
380
+ ```sh
381
+ bun add @rakun-kit/manager-locales
382
+ # or: npm install @rakun-kit/manager-locales
383
+ ```
384
+
385
+ Import the required language subpath and register it with `managerLanguages`;
386
+ the public `manager.uiLocales` operation returns configured packs to the
387
+ manager client. Its safe runtime configuration also includes the public SEO
388
+ `siteUrl` and the configured home-page variant group, when available, so the
389
+ manager can expose its Visit site action and classify SEO fallbacks correctly:
390
+
391
+ ```ts
392
+ import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
393
+
394
+ rakunBootstrap({
395
+ // ...
396
+ managerLanguages: [esManagerLocalePack],
397
+ })
398
+ ```
399
+
400
+ `managerLanguages` may also extend locales with arbitrary project keys. This is
401
+ useful for translatable content-type titles and categories without adding host
402
+ keys to the manager's static `ManagerMessageKey` union:
403
+
404
+ ```ts
405
+ import { extendManagerLanguagePack } from '@rakun-kit/core/contracts'
406
+ import { esManagerLocalePack } from '@rakun-kit/manager-locales/es'
407
+
408
+ rakunBootstrap({
409
+ // ...
410
+ managerLanguages: [
411
+ {
412
+ code: 'en',
413
+ name: 'English',
414
+ messages: {
415
+ 'field.title': 'Title',
416
+ 'layoutModule.header': 'Header',
417
+ 'project.contentTypes.article.menu': 'Articles',
418
+ },
419
+ },
420
+ extendManagerLanguagePack(esManagerLocalePack, {
421
+ 'field.title': 'Título',
422
+ 'layoutModule.header': 'Cabecera',
423
+ 'project.contentTypes.article.menu': 'Artículos',
424
+ }),
425
+ ],
426
+ })
427
+ ```
428
+
429
+ Content-type field labels automatically use `field.<fieldName>` from these
430
+ project messages. Missing translations fall back to a built-in manager label
431
+ when available and otherwise to the humanized field name.
432
+
433
+ Route layout module labels use `layoutModule.<layoutKey>`, so a slot configured
434
+ with `key: 'header'` resolves `layoutModule.header`.
435
+
436
+ ## Plugins
437
+
438
+ Trusted server plugins contribute to the same bootstrap registry without coupling
439
+ core to React:
440
+
441
+ ```ts
442
+ import { defineRakunPlugin, rakunBootstrap } from '@rakun-kit/core'
443
+
444
+ export const analyticsPlugin = defineRakunPlugin({
445
+ id: '@acme/rakun-analytics',
446
+ contentTypes: [AnalyticsEvent],
447
+ routes: analyticsRoutes,
448
+ apiOperations: analyticsOperations,
449
+ permissions: ['plugin.analytics.view'],
450
+ literals: {},
451
+ initialize: async ({ db }) => {
452
+ // Services and migrations are ready here. Keep initialization idempotent.
453
+ },
454
+ })
455
+
456
+ rakunBootstrap({
457
+ plugins: [analyticsPlugin],
458
+ contentTypes: [],
459
+ literals: {},
460
+ mongo,
461
+ })
462
+ ```
463
+
464
+ Plugin ids and contributed content types, routes, operations, literals, and
465
+ custom field editor ids must be unique. Rakun reports both owners on conflicts.
466
+ Visual manager and web facets are registered separately in their browser
467
+ runtimes.
468
+
469
+ Custom field factories can use `createPluginField`. Their serializable
470
+ `meta.editor` must match a field declaration in the server plugin and a React
471
+ editor registered by its manager facet. Each field also declares capabilities
472
+ that shared systems consume instead of branching on its built-in type:
473
+
474
+ ```ts
475
+ const externalLink = createPluginField({
476
+ meta: {
477
+ type: 'Link',
478
+ ui: 'Link',
479
+ editor: '@acme/external-link',
480
+ capabilities: {
481
+ valueKind: 'object',
482
+ dynamic: {
483
+ properties: { title: 'string', href: 'string' },
484
+ mapProperties: true,
485
+ },
486
+ },
487
+ },
488
+ schemas: sameSchemas(() => ExternalLinkSchema),
489
+ runtime: {
490
+ populate: (value, { populateLink }) => populateLink(value),
491
+ },
492
+ })
493
+ ```
494
+
495
+ `valueKind` controls Dynamic Data compatibility. `dynamic.properties` exposes
496
+ nested source paths, `mapProperties` makes those paths independent mapping
497
+ targets, `relation` enables content-type traversal, and `collection` describes
498
+ `homogeneous` or `heterogeneous` per-item mapping. Capabilities are encoded for
499
+ the manager. `runtime.populate` remains server-only and receives the database,
500
+ recursive `populate`, and built-in `populateLink` helpers. The web pipeline
501
+ exports this generic phase as `populateFields`; `populateLinks` remains a
502
+ deprecated compatibility alias.
503
+
504
+ ## Content Types
505
+
506
+ A `ContentType` defines a logical collection:
507
+
508
+ ```ts
509
+ const Post = new ContentType({
510
+ name: 'Post',
511
+ menu: {
512
+ title: 'Posts',
513
+ icon: 'newspaper',
514
+ category: 'Content',
515
+ },
516
+ fields: {
517
+ title: f.string(),
518
+ slug: f.string().type('Slug'),
519
+ body: f.string().type('RichText').optional(),
520
+ published: f.boolean().optional(),
521
+ },
522
+ uniques: [['slug']],
523
+ listFields: ['title', 'slug', 'published'],
524
+ })
525
+ ```
526
+
527
+ Page-like content types can define ordered page modules with `iterator` outside
528
+ `fields`. Rakun persists this generated field as `_iterator`:
529
+
530
+ ```ts
531
+ import { StructuredData } from '@rakun-kit/core/internal-content-types'
532
+
533
+ const Page = new ContentType({
534
+ name: 'Page',
535
+ fields: {
536
+ title: f.string(),
537
+ slug: f.string().type('Slug'),
538
+ },
539
+ iterator: [
540
+ { contentType: PageSection, type: 'new' },
541
+ { contentType: Hero, type: 'new' },
542
+ { contentType: LayoutWithInfo, type: 'new' },
543
+ { contentType: Newsletter, type: 'new' },
544
+ { contentType: StructuredData, type: 'new' },
545
+ ],
546
+ })
547
+ ```
548
+
549
+ `StructuredData` is a built-in iterator module for JSON-LD. It provides typed
550
+ Product, Article, Organization, WebSite, and BreadcrumbList forms plus a Custom
551
+ JSON form. Add it to a shared Template and map its fields from the current
552
+ document to reuse one schema across every page of the content type. The official
553
+ React and Next renderers handle this module natively, so applications do not
554
+ create a matching component file.
555
+
556
+ `iterator` always belongs to the individual document and is edited in the
557
+ manager's Content tab. When the content type has a configured `hasPage: true`
558
+ route, Rakun automatically enables a separate shared Template tab with the
559
+ same module picker as Content. The manager also offers one special Content slot
560
+ at the template root and inside every `f.blocks(...)` field. The template must
561
+ contain that slot exactly once; web output replaces it with the current
562
+ document's iterator modules. This supports compositions such as a shared hero,
563
+ a `LayoutWithInfo` wrapper containing the unique content, and a shared
564
+ newsletter. Route layout modules such as header and footer still wrap the
565
+ assembled result.
566
+
567
+ Iterator and template modules can also be made conditional from the manager. A
568
+ condition is stored on the module entry and evaluated against the current
569
+ document:
570
+
571
+ ```ts
572
+ {
573
+ name: "Credits",
574
+ value: { /* module relation */ },
575
+ visibleWhen: {
576
+ field: "credits",
577
+ operator: "notEmpty",
578
+ },
579
+ }
580
+ ```
581
+
582
+ Supported operators are `notEmpty` and `empty`. Conditional modules remain in
583
+ their stored Content or Template composition but are omitted from web and
584
+ preview output when their condition does not match.
585
+
586
+ Main properties:
587
+
588
+ - `name`: stable type name. Also used as `_type`.
589
+ - `fields`: field map.
590
+ - `iterator`: page module entries. Generates the reserved `_iterator` field.
591
+ - `menu`: manager metadata.
592
+ - `uniques`: unique field groups.
593
+ - `listFields`: preferred fields in lists and relations.
594
+ - `hideFromManager()`: hides the content type from manager content type lists.
595
+ - `apiOnly()`: applies `.apiOnly()` to every field in the content type.
596
+ - `managerOnly()`: applies `.managerOnly()` to every field in the content type.
597
+ - Saved manager documents support comments and user mentions by default.
598
+ - `withHooks()`: attaches lifecycle hooks such as `beforeInsert`, `beforeUpdate`, and `onGet`.
599
+ - Dynamic data bindings are available on manager-visible fields by default; use
600
+ field-level `.noDynamic()` to opt out.
601
+ - A string field can initialize a new document's SEO dynamic data with
602
+ `.seo('title')`, `.seo('description')`, or another string field from Rakun's
603
+ built-in SEO model. This does not change existing documents.
604
+
605
+ When a content type has a configured route with `hasPage: true`, Rakun adds an
606
+ optional reserved `_seo` relation automatically. If that content type has an
607
+ `iterator`, Rakun also enables its shared Template editor automatically.
608
+
609
+ Hooks run around DB mutations and public output resolution:
610
+
611
+ ```ts
612
+ const User = new ContentType({
613
+ name: 'User',
614
+ fields: {
615
+ email: f.string().type('Email').required(),
616
+ password: f.string().type('Password').required().managerOnly(),
617
+ },
618
+ }).withHooks({
619
+ beforeInsert: ({ data }) => ({
620
+ ...data,
621
+ password: hashPassword(String(data.password)),
622
+ }),
623
+ onGet: ({ data }) => ({
624
+ ...data,
625
+ displayName: String(data.email).split('@')[0],
626
+ }),
627
+ })
628
+ ```
629
+
630
+ Dynamic data turns a content type into a reusable layout. The manager can bind
631
+ manager-visible fields to another content item field, or to a generated `href`
632
+ when a source content type has a page route. Fields are dynamic by default; call
633
+ `.noDynamic()` on fields that should not accept bindings or be exported as
634
+ source paths. Source content types are hidden by default; set
635
+ `dynamicDataSource: true` on content types that should appear in the manager
636
+ source selector. Field bindings can also select `Current document` to read values
637
+ from the document being edited without marking its content type as a source.
638
+
639
+ Source field selectors are type-aware. A string target only offers string-like
640
+ source paths, number targets only offer numbers, and boolean targets only offer
641
+ booleans. Object-like source fields are traversed so nested leaf fields can be
642
+ selected, while reserved SEO metadata is omitted from dynamic data mappings.
643
+ The generated `href` source is only shown for content types that have a
644
+ configured route with `hasPage: true`.
645
+
646
+ In list mappings, a target `f.link()` is exposed as two independent target
647
+ paths, `<field>.title` and `<field>.href`. Mapping those paths reconstructs the
648
+ target link object instead of assigning a URL string to the complete field.
649
+
650
+ Homogeneous `f.array(f.link())` and `f.array(f.relation(...))` targets can keep
651
+ using a direct array field binding or switch to per-item list mapping.
652
+ `f.relation(...).multiple()` is the same relation-array model and has the same
653
+ mapping support. Link arrays expose `title` and `href` for every item; relation
654
+ arrays expose the related content type's fields. Their resolved items stay flat,
655
+ whereas heterogeneous `f.blocks(...)` items keep their `{ name, value }`
656
+ wrapper.
657
+
658
+ For example, a routeable category can pre-link its SEO title when a new category
659
+ is created:
660
+
661
+ ```ts
662
+ const Category = new ContentType({
663
+ name: 'Category',
664
+ fields: {
665
+ title: f.string().required().seo('title'),
666
+ },
667
+ })
668
+ ```
669
+
670
+ The manager stores this as a normal editable dynamic data binding from
671
+ `seo.title` to `Current document · title`.
672
+
673
+ ```ts
674
+ const Project = new ContentType({
675
+ name: 'Project',
676
+ dynamicDataSource: true,
677
+ fields: {
678
+ title: f.string().required(),
679
+ slug: f.string().type('Slug').required(),
680
+ },
681
+ })
682
+
683
+ const Carousel = new ContentType({
684
+ name: 'Carousel',
685
+ fields: {
686
+ title: f.string().required(),
687
+ internalNote: f.string().noDynamic(),
688
+ items: f.blocks([
689
+ {
690
+ name: 'CarouselItem',
691
+ field: f.relation(CarouselItem, 'new'),
692
+ },
693
+ ]),
694
+ },
695
+ })
696
+ ```
697
+
698
+ List bindings append dynamic items to manually stored items instead of replacing
699
+ the list. If the same stable item id appears in both sources, the dynamically
700
+ resolved item wins and the duplicate manual copy is skipped.
701
+
702
+ A list inside a nested module can also use a compatible relation or blocks array
703
+ from the current document. The manager exposes these sources as
704
+ `Current document · <field>`, infers the content type of each array item, and
705
+ lets each item be mapped with the same field mapping UI used for collections.
706
+ These bindings store a `currentDocument` source alongside the inferred item
707
+ content type:
708
+
709
+ ```ts
710
+ const headerBindings = {
711
+ lists: {
712
+ categories: {
713
+ contentType: LinkItem.name,
714
+ source: {
715
+ kind: 'currentDocument',
716
+ contentType: Project.name,
717
+ path: 'categories',
718
+ itemName: 'Category',
719
+ },
720
+ itemName: 'Category',
721
+ map: {
722
+ title: { contentType: LinkItem.name, path: 'title' },
723
+ href: { contentType: LinkItem.name, path: 'href' },
724
+ },
725
+ },
726
+ },
727
+ }
728
+ ```
729
+
730
+ List query conditions can compare a source field with a value from the current
731
+ context. At the top level this is the current document; inside a nested list it
732
+ is the parent source item and the manager labels it `Current item`. Programmatic
733
+ bindings use `{ $current: "path.to.field" }`. For example, a category can query
734
+ only the projects whose related category has the same slug:
735
+
736
+ ```ts
737
+ query: {
738
+ filter: {
739
+ "category.slug": { $current: "slug" },
740
+ },
741
+ options: { limit: 10 },
742
+ }
743
+ ```
744
+
745
+ Nested conditions can still read the root document by selecting `Current
746
+ document`, which stores `{ $document: "path.to.field" }`. Current-item and
747
+ current-document paths are checked against their respective content type's
748
+ dynamic field rules before the query runs. `_id` is also available for relation
749
+ queries.
750
+
751
+ List mappings can be nested recursively when a mapped target field is itself a
752
+ `blocks` list, link array, or relation array. Use `kind: "list"` for that map
753
+ entry and configure its source, query, item type, and field map exactly like a
754
+ top-level list binding. Inside the nested query, `$current` refers to the source
755
+ item being mapped by the parent list:
756
+
757
+ ```ts
758
+ map: {
759
+ title: { contentType: Category.name, path: "title" },
760
+ images: {
761
+ kind: "list",
762
+ contentType: Project.name,
763
+ itemName: "CategoriesGalleryItemImage",
764
+ query: {
765
+ filter: { "category._id": { $current: "_id" } },
766
+ options: { limit: 10, sort: { title: "asc" } },
767
+ },
768
+ map: {
769
+ title: { contentType: Project.name, path: "title" },
770
+ href: { contentType: Project.name, virtual: "href" },
771
+ image: { contentType: Project.name, path: "image" },
772
+ },
773
+ },
774
+ }
775
+ ```
776
+
777
+ The manager exposes this as the `Nested list` mapping mode. Nested mappings
778
+ apply the same source opt-in, field compatibility, query validation, and
779
+ `.noDynamic()` rules at every level.
780
+
781
+ A list mapping can also collect an array through a reverse relation without
782
+ persisting that relation on the source document. For example, a category gallery
783
+ can create one item per `Category` and collect the images of its related
784
+ `Project` documents:
785
+
786
+ ```ts
787
+ const Category = new ContentType({
788
+ name: 'Category',
789
+ dynamicDataSource: true,
790
+ fields: {
791
+ title: f.string().required(),
792
+ },
793
+ })
794
+
795
+ const Project = new ContentType({
796
+ name: 'Project',
797
+ dynamicDataSource: true,
798
+ fields: {
799
+ category: f.relation(Category, 'existing').required(),
800
+ images: f.file().type('Image').multiple().required(),
801
+ },
802
+ })
803
+
804
+ const galleryBindings = {
805
+ lists: {
806
+ items: {
807
+ contentType: Category.name,
808
+ itemName: 'CategoriesGalleryItem',
809
+ map: {
810
+ title: { contentType: Category.name, path: 'title' },
811
+ images: {
812
+ kind: 'relatedCollection',
813
+ contentType: Project.name,
814
+ relation: 'category',
815
+ path: 'images',
816
+ limit: 10,
817
+ },
818
+ },
819
+ },
820
+ },
821
+ }
822
+ ```
823
+
824
+ The related collection query matches `Project.category._id` against the current
825
+ category, preserves project and image order, and flattens the selected array by
826
+ one level. Its numeric limit applies to related projects and is capped at 100.
827
+
828
+ Schema and validation methods:
829
+
830
+ - `getInputSchema()`: write schema. Includes `_type`, `createdBy`, and `updatedBy`.
831
+ - `getSchema()`: persisted schema.
832
+ - `getPopulatedSchema()`: output with populated relations and `_id`.
833
+ - `getOutputSchema()`: public API output.
834
+ - `getOutputSchemaWithoutIterators()`: output variant without iterator fields.
835
+ - `validate`, `partialValidate`, `validateOutput`: Zod helpers.
836
+
837
+ The registry lives in `lib/Registry`:
838
+
839
+ - `registerContentType`, `registerInternalContentType`.
840
+ - `getContentTypes`, `getExternalContentTypes`, `getInternalContentTypes`.
841
+ - `getContentTypesForManager`: returns serializable content types without Zod schemas and without content types marked with `hideFromManager()`.
842
+ - `getContentTypeByName`.
843
+
844
+ ## Fields
845
+
846
+ Use the concise `f` namespace for field factories. `Fields` remains exported as
847
+ a backward-compatible alias and references the same object.
848
+
849
+ Main factories:
850
+
851
+ ```ts
852
+ f.string()
853
+ f.number()
854
+ f.boolean()
855
+ f.date()
856
+ f.select(['draft', 'published'])
857
+ f.relation(Post)
858
+ f.contentReference('Post')
859
+ f.selfRelation()
860
+ f.blocks([{ name: 'title', field: f.string() }])
861
+ f.array(f.string())
862
+ f.link()
863
+ f.menu()
864
+ f.file()
865
+ f.breadcrums()
866
+ ```
867
+
868
+ Common modifiers:
869
+
870
+ - Fields are required by default. Use `.optional()` when a value may be omitted.
871
+ - `.required()` explicitly marks a field as required. It remains available for
872
+ clarity and backward compatibility.
873
+ - `.translatable()`: stores values per language with shape `{ _tag: "Translatable", ... }`.
874
+ - `.apiOnly()`: available for API/persistence, hidden from manager.
875
+ - `.managerOnly()`: visible to manager, excluded from API output.
876
+
877
+ Notable fields:
878
+
879
+ - `StringField`: UI `Text`, `Textarea`, `RichText`, `Email`, `Slug`, `Password`, `Id`, `Url`; supports `.min()` and `.max()`.
880
+ - `NumberField`: supports `.min()` and `.max()`.
881
+ - `DateField`: supports `.type("Date")`, `.type("DateTime")` and
882
+ `.type("Time")`. Date and datetime writes accept JavaScript `Date` values or
883
+ their ISO datetime JSON representation and normalize them to `Date`; time
884
+ values remain ISO time strings.
885
+ - `RelationField`: relation to another `ContentType`; accepts existing references or inline creation. `f.relation(Post, "existing")` restricts to existing records; `"new"` restricts to new records. `.multiple()` returns a homogeneous array of relations.
886
+ - `ContentReferenceField`: reference by content type name.
887
+ - `LinkField`: the manager stores a direct `{ href, title }` value or an
888
+ internal `{ routeId, contentTypeId, title }` reference. Titled links resolve
889
+ to `{ href, title }` in web output, with internal `href` values localized by
890
+ route. New input and web output always use an object shape. Persisted legacy
891
+ direct URL strings remain readable and receive an empty `title` when loaded
892
+ by the manager or normalized for web output.
893
+ - `MenuField`: `f.menu()` stores an ordered navigation tree. Every node is an
894
+ internal or direct link plus `children`, for example
895
+ `{ title, href, children: [...] }`. Internal references use `routeId` and
896
+ `contentTypeId` while persisted and resolve recursively to localized `href`
897
+ values in web output. The manager supports drag-and-drop ordering and
898
+ unlimited nested levels.
899
+ - `BreadcrumsField`: `f.breadcrums()` is a computed, API-only field for page
900
+ modules. In web and preview output it returns the localized route hierarchy as
901
+ `{ label, href }[]`, ordered from the highest ancestor to the current page. It
902
+ returns `null` when the content is resolved outside a routable page and is
903
+ never accepted as input, persisted, or rendered in the manager.
904
+ - `FileField`: integrates media and optimization options.
905
+ - `f.blocks(...)`: heterogeneous ordered list. Each item stores a `name` and a `value`, and the value can match one of the named field shapes. Use it for block-like content where different item types can appear in the same list.
906
+ - `f.array(...)`: homogeneous ordered list. Every item uses the same field shape. Chain `.min(count)` and `.max(count)` to validate its item count. The same limits are available after `.multiple()` on relation, file, select, and content-reference fields, for example `f.file().multiple().min(1).max(4)`.
907
+ - `IteratorField`: repeatable structure based on content type entries.
908
+
909
+ ## Derived Types
910
+
911
+ `@rakun-kit/core/types` and `lib/types` derive types from `ContentType`:
912
+
913
+ - `DataInput<T>`: accepted shape for create/update.
914
+ - `DBOutput<T>`: persisted shape with metadata (`_id`, `_type`, timestamps, authorship).
915
+ - `DataPopulated<T>`: shape with populated relations.
916
+ - `DataFront<T>`: public output without `apiOnly` fields.
917
+ - `Filter<T>`, `Query<T>`, `ListInput<T>`, `GetAllInput<T>`: typed filters and list inputs.
918
+ - `MaybeTranslatableValue<T>`, `TranslatableValue<T>`.
919
+
920
+ ## ORM and Mongo
921
+
922
+ `core/src/orm` implements `DBService` on top of MongoDB:
923
+
924
+ ```ts
925
+ const db = await getMongoService()
926
+
927
+ const post = await db.create(Post, {
928
+ _type: 'Post',
929
+ title: 'Hello',
930
+ slug: 'hello',
931
+ })
932
+ ```
933
+
934
+ Operations:
935
+
936
+ - `get(contentType, id, fields?)`
937
+ - `list(contentType, query)`
938
+ - `create(contentType, data)`
939
+ - `update(contentType, id, data)`
940
+ - `updateMany(contentType, filter, data)`
941
+ - `delete(contentType, filter)`
942
+ - `find(contentType, filter, fields?)`
943
+ - `clear(contentType)`
944
+ - `findDependencies(contentType, id)`
945
+ - `upsert(contentType, filter, data)`
946
+ - `getAll(contentType, query?)`
947
+
948
+ Connection:
949
+
950
+ - `createMongoConnection(config)`: stores config.
951
+ - `createMongoService(config)`: connects and creates handlers.
952
+ - `getMongoService()`: returns the singleton or creates it from config.
953
+ - `closeMongoService()`: closes the connection and clears the singleton.
954
+ - `shutdownRakun()`: releases all core runtime resources for graceful host
955
+ shutdown or restart.
956
+
957
+ `MongoConfig`:
958
+
959
+ ```ts
960
+ type MongoConfig = {
961
+ MONGO_URI: string
962
+ ENVIRONMENT?: 'local' | 'development' | 'test' | 'production'
963
+ clientOptions?: MongoClientOptions
964
+ }
965
+ ```
966
+
967
+ `clientOptions` is forwarded to the official MongoDB driver. Rakun defaults
968
+ `maxIdleTimeMS` to 60 seconds, releasing unused pooled sockets; an explicit
969
+ value overrides the default. The topology-monitoring connection stays active
970
+ until `closeDatabase()`, `closeMongoService()`, or `shutdownRakun()` runs. In
971
+
972
+ In environments other than `test`, the connection creates indexes defined by `createIndexes`.
973
+
974
+ ## API Operations
975
+
976
+ `api/operations` defines typed contracts and handlers for endpoints:
977
+
978
+ - Manager: CRUD, auth, MFA, media, literals, settings.
979
+ - Manager comments and mentions: `manager.comments.list`,
980
+ `manager.comments.create`, `manager.comments.toggleReaction`,
981
+ `manager.comments.markRead`, `manager.comments.unreadCount`,
982
+ `manager.users.mentions`,
983
+ `manager.notifications.list`, and `manager.notifications.markRead`.
984
+ - Web: page resolution, static paths, sitemap, robots, `llms.txt`, and preview.
985
+
986
+ `web.staticPaths` returns `{ path, ttl }` only for route-map entries backed by
987
+ page routes configured with `dynamic: false`. Adapters can use this operation
988
+ for static generation without querying Rakun's database directly. Monolithic
989
+ server adapters can instead call `getRakunWebStaticPaths`, `getRakunWebPage`,
990
+ and `getRakunWebPreviewPage` after bootstrapping and initializing core.
991
+
992
+ `web.llms` returns the optional site-level `llms.txt` document configured in
993
+ Manager Settings. Rakun renders a curated Markdown title, summary, guidance,
994
+ sections, and links; it does not dump the sitemap automatically. Internal links
995
+ are resolved for the requested language and become absolute when SEO `siteUrl`
996
+ is configured. Links without a public route are omitted without hiding their
997
+ section heading. Optional sections retain their titles beneath the conventional
998
+ `## Optional` heading. The llms title and summary fall back to SEO `siteName` and the
999
+ default SEO description. The result is `null` while publishing is disabled or
1000
+ there is no usable title. Server adapters can call `getRakunWebLlmsTxt`
1001
+ directly after initialization.
1002
+
1003
+ `manager.localeVariants.list` includes an optional `path` on each language
1004
+ assignment when that exact assigned document is published and has a generated
1005
+ page route. Manager clients can use it for a contextual public-page link without
1006
+ granting editors direct access to internal `RouteMap` records.
1007
+
1008
+ Routeable content with document visibility is public only when `_visibility`
1009
+ is explicitly `published` or `hidden`. Missing visibility is treated as draft,
1010
+ both while generating route maps and when resolving a public page, so legacy or
1011
+ incomplete records fail closed even if an obsolete route-map entry remains.
1012
+
1013
+ Main helpers:
1014
+
1015
+ - `defineOperationContract`: declares a contract with Zod input/output, method, path, and metadata.
1016
+ - `defineOperation`: combines a contract with its implementation.
1017
+ - `createRakunApiClient`: creates a browser/server HTTP client for custom operations.
1018
+ - `GetClient`: derives a typed client from an operation map.
1019
+ - `createManagerOperationContracts`, `createWebOperationContracts`.
1020
+ - `createManagerOperationDefinitions`, `createWebOperationDefinitions`.
1021
+ - `createRakunOperationDefinitions`: combines manager + web.
1022
+ - `createOperationManifest`: serializable metadata for clients.
1023
+ - `createOperationPath`: HTTP path from operation name.
1024
+ - `mergeOperationContracts`: combines maps.
1025
+
1026
+ `@rakun-kit/core/manager` exposes the manifest and name-based types:
1027
+
1028
+ ```ts
1029
+ import {
1030
+ managerOperationManifest,
1031
+ getManagerOperationMeta,
1032
+ type ManagerOperationInput,
1033
+ type ManagerOperationOutput,
1034
+ } from '@rakun-kit/core/manager'
1035
+ ```
1036
+
1037
+ ### Custom Operations
1038
+
1039
+ Applications can define operations in a separate object, pass that object to
1040
+ bootstrap, and reuse its type on the frontend:
1041
+
1042
+ ```ts
1043
+ // server/api-operations.ts
1044
+ import { defineOperation } from '@rakun-kit/core'
1045
+ import { z } from 'zod'
1046
+
1047
+ export const apiOperations = {
1048
+ 'demo.helloWorld': defineOperation<
1049
+ { text: string },
1050
+ { message: string },
1051
+ 'query',
1052
+ 'get',
1053
+ 'public'
1054
+ >({
1055
+ access: 'public',
1056
+ kind: 'query',
1057
+ method: 'get',
1058
+ description: 'Return a hello world message with the provided text',
1059
+ input: z.object({
1060
+ text: z.string().default('world'),
1061
+ }),
1062
+ output: z.object({
1063
+ message: z.string(),
1064
+ }),
1065
+ resolve: ({ input }) => ({
1066
+ message: `Hello ${input.text}`,
1067
+ }),
1068
+ }),
1069
+ }
1070
+ ```
1071
+
1072
+ ```ts
1073
+ // bootstrap
1074
+ import { apiOperations } from './server/api-operations'
1075
+
1076
+ rakunBootstrap({
1077
+ // ...
1078
+ apiOperations,
1079
+ })
1080
+ ```
1081
+
1082
+ Operation names define their HTTP path: `demo.helloWorld` becomes
1083
+ `/demo/helloWorld`.
1084
+
1085
+ Rules:
1086
+
1087
+ - Names must not collide with built-in operations.
1088
+ - Operations prefixed with `manager.` are included in manager operation routers.
1089
+ - Operations prefixed with `web.` are included in web operation routers.
1090
+ - Operations with any other prefix are included when using the combined operation registry.
1091
+ - Use `access: "public"` for operations called from public web pages.
1092
+ - Use `access: "auth"` for operations that require a manager session.
1093
+
1094
+ The manager route `manager.apiOperations` returns a JSON-serializable operation
1095
+ catalog for manager API docs/playgrounds. Input and output Zod schemas are
1096
+ converted to JSON Schema for display.
1097
+
1098
+ ### Typed API Client
1099
+
1100
+ `@rakun-kit/core/web` exposes a small typed HTTP client for operation maps:
1101
+
1102
+ ```ts
1103
+ import { createRakunApiClient, type GetClient } from '@rakun-kit/core/web'
1104
+ import type { apiOperations } from './server/api-operations'
1105
+
1106
+ type ApiClient = GetClient<typeof apiOperations>
1107
+
1108
+ const client: ApiClient = createRakunApiClient<typeof apiOperations>({
1109
+ baseUrl: '/api',
1110
+ })
1111
+
1112
+ const result = await client.query('demo.helloWorld', {
1113
+ text: 'Rakun',
1114
+ })
1115
+
1116
+ result.message
1117
+ ```
1118
+
1119
+ The client exposes:
1120
+
1121
+ - `query(name, input?, options?)`: only accepts operations with `kind: "query"`.
1122
+ - `mutation(name, input?, options?)`: only accepts operations with `kind: "mutation"`.
1123
+
1124
+ The client derives input and output types from the Zod schemas in
1125
+ `apiOperations`.
1126
+
1127
+ ## Request Context and Auth
1128
+
1129
+ `createRequestContext(input)` normalizes headers, cookies, and response:
1130
+
1131
+ ```ts
1132
+ const ctx = await createRequestContext({
1133
+ headers: req.headers,
1134
+ cookies,
1135
+ res: {
1136
+ setHeader: res.setHeader.bind(res),
1137
+ cookie: res.cookie.bind(res),
1138
+ },
1139
+ })
1140
+ ```
1141
+
1142
+ The resulting context includes:
1143
+
1144
+ - `req.headers`, `req.cookies`
1145
+ - `res.setHeader`, `res.cookie`
1146
+ - `user`: manager user or `null`
1147
+ - `getUser()`: returns the user or throws `AUTH_REQUIRED`
1148
+
1149
+ Session cookies are managed with `getSessionCookie` and `setSessionCookie`.
1150
+
1151
+ ## Media
1152
+
1153
+ Media uses a storage adapter:
1154
+
1155
+ ```ts
1156
+ createMediaService({
1157
+ adapter,
1158
+ defaultAccess: 'private',
1159
+ defaultGetExpiresInSeconds: 300,
1160
+ uploadUrl: '/api/rakun/manager/media/upload',
1161
+ })
1162
+ ```
1163
+
1164
+ APIs:
1165
+
1166
+ - `createMediaConnection(config)`: stores config without creating the service.
1167
+ - `createMediaService(config)`: creates the singleton with an adapter.
1168
+ - `getMediaService()`: returns the singleton or creates it from config.
1169
+ - `handleMediaBinaryUpload`: processes manager binary uploads.
1170
+
1171
+ The service supports prepare/finalize upload, URL generation, folders, and media optimization depending on adapter/configuration. Manager uploads encode original file names for transport, so Unicode names (including accents, non-Latin scripts, and emoji) are preserved without placing invalid characters in HTTP headers.
1172
+
1173
+ Adapters can implement `getPublicObject` to let every Rakun server adapter proxy
1159
1174
  public media at its normal `/media/<key>` API route. This keeps browser
1160
- URLs stable even when the object store is private or only supports expiring
1161
- presigned URLs. The core handler validates the key under `public/`, forwards
1162
- byte ranges, preserves object metadata, and applies long-lived caching by
1163
- default. The storage adapter receives server-side credentials; never expose its
1164
- presigned storage URL as the public media URL.
1165
-
1166
- Optimized video uploads use the `ffmpeg-static` peer dependency and produce an MP4 primary object plus MP4 and WebM entries in `sources`. Existing image-only optimize options remain compatible; use `video: { quality: 80 }` to configure video quality explicitly.
1167
-
1168
- Existing images and videos can be reimported from the media manager with the selected optimization settings. The replacement uses new storage keys and updates the existing `Media` record only after every requested variant has been written, preserving its ID and content references. Preview generation stores a compact `data:image/...` string on `previewUrl` for LQIP use in `@rakun-kit/react` `Image`, instead of writing a separate preview object to storage.
1169
-
1170
- The `manager.media.replace` mutation accepts a newly uploaded image and swaps it into an existing `Media` record. It preserves the media ID, name, title, alt text, folder, status, and all content references; after the database points to the new storage keys, Rakun removes the previous primary object and generated variants and revalidates directly dependent documents when route revalidation is configured.
1171
-
1172
- ## Persistent Event Log
1173
-
1174
- Rakun keeps business/audit events separate from its technical console logger.
1175
- The event log keeps events immutable and defaults to a MongoDB collection
1176
- with indexes for time, type, category, outcome, severity, correlation and tags:
1177
-
1178
- ```ts
1179
- import { recordEvent, queryEvents } from '@rakun-kit/core'
1180
-
1181
- await recordEvent({
1182
- type: 'content.article.published',
1183
- category: 'content',
1184
- outcome: 'success',
1185
- actor: { type: 'manager-user', id: userId },
1186
- resource: { type: 'Article', id: articleId },
1187
- correlationId: requestId,
1188
- tags: ['editorial'],
1189
- data: {
1190
- locale: 'es',
1191
- changedFields: 3,
1192
- },
1193
- })
1194
-
1195
- const page = await queryEvents({
1196
- categories: ['content'],
1197
- outcomes: ['success'],
1198
- from: new Date('2026-01-01T00:00:00.000Z'),
1199
- limit: 50,
1200
- })
1201
- ```
1202
-
1203
- `data` accepts nested JSON values. Event queries use cursor pagination and can
1204
- filter by types, categories, severities, outcomes, sources, correlation,
1205
- required tags and a date range. A custom persistence implementation can be
1206
- plugged in globally:
1207
-
1208
- ```ts
1209
- import type { EventLogAdapter } from '@rakun-kit/core'
1210
-
1211
- const adapter: EventLogAdapter = {
1212
- async append(event) {
1213
- return customStore.append(event)
1214
- },
1215
- async query(filters) {
1216
- return customStore.query(filters)
1217
- },
1218
- async deleteBefore(before) {
1219
- return customStore.deleteBefore(before)
1220
- },
1221
- }
1222
-
1223
- rakunBootstrap({
1224
- // ...
1225
- eventLog: { adapter },
1226
- })
1227
- ```
1228
-
1229
- Plugins receive the resolved `eventLog` service in their initialization
1230
- context. Reading a shared event stream should be protected with the built-in
1231
- `system.eventLog.read` permission. The built-in `manager.logs.list` operation and the
1232
- manager Settings → Logs screen both enforce it.
1233
-
1234
- Retention cleanup is available from the manager Logs screen to users with the
1235
- built-in `system.eventLog.manage` permission. It deletes events strictly older
1236
- than the selected cutoff. Custom adapters must implement `deleteBefore` to
1237
- support this action. The cleanup mutation itself is logged after deletion.
1238
-
1239
- Failed API operations are persisted automatically as `api.operation.failed`
1240
- events. This includes expected 4xx application errors and unexpected 5xx
1241
- failures across the core operation wrapper and the Express, Next.js, and tRPC
1242
- adapters. Events include the operation, status, method, kind, correlation ID,
1243
- and authenticated actor when available. Request payloads, application error
1244
- causes, and raw internal error messages are not persisted.
1245
-
1246
- Every successful API mutation is also persisted with an operation-specific
1247
- event type such as `manager.create.succeeded` or
1248
- `manager.backups.restore.succeeded`. Successful queries are not recorded by
1249
- default, which keeps the event stream focused on state changes. Mutation
1250
- events contain operation metadata and actor/correlation context, but never
1251
- copy the input payload or result.
1252
-
1253
- ## Mail
1254
-
1255
- Mail providers receive normalized, already-rendered messages through
1256
- `MailAdapter`, so neither `core` nor an adapter depends on React or a template
1257
- engine:
1258
-
1259
- ```ts
1260
- import type { MailAdapter } from '@rakun-kit/core'
1261
-
1262
- const adapter: MailAdapter = {
1263
- async send(message) {
1264
- const result = await provider.send(message)
1265
- return { id: result.id }
1266
- },
1267
- }
1268
-
1269
- rakunBootstrap({
1270
- // ...
1271
- mail: {
1272
- adapter,
1273
- defaultFrom: 'hello@example.com',
1274
- defaultReplyTo: 'support@example.com',
1275
- },
1276
- })
1277
- ```
1278
-
1279
- Send rendered content directly:
1280
-
1281
- ```ts
1282
- import { sendMail } from '@rakun-kit/core'
1283
-
1284
- await sendMail({
1285
- to: 'ada@example.com',
1286
- subject: 'Welcome',
1287
- html: '<p>Hello Ada</p>',
1288
- text: 'Hello Ada',
1289
- })
1290
- ```
1291
-
1292
- Or create a typed application template registry:
1293
-
1294
- ```ts
1295
- import { createMailSender, defineMailTemplate } from '@rakun-kit/core'
1296
-
1297
- const mail = createMailSender({
1298
- templates: {
1299
- welcome: defineMailTemplate<{ name: string }>({
1300
- subject: ({ name }) => `Welcome, ${name}`,
1301
- render: ({ name }) => ({
1302
- html: `<p>Hello ${name}</p>`,
1303
- text: `Hello ${name}`,
1304
- }),
1305
- }),
1306
- },
1307
- })
1308
-
1309
- await mail.send({
1310
- template: 'welcome',
1311
- props: { name: 'Ada' },
1312
- to: 'ada@example.com',
1313
- })
1314
- ```
1315
-
1316
- The common contract supports To/CC/BCC/Reply-To, custom headers and in-memory
1317
- `Uint8Array` attachments. Sending is immediate; queues, retries and delivery
1318
- events belong to application infrastructure.
1319
-
1320
- Every mail sent through a bootstrapped Rakun mail service creates append-only
1321
- `mail.send.attempted` and `mail.send.succeeded` or `mail.send.failed` events.
1322
- They share a correlation id and include only operational counts, provider,
1323
- template and duration. Recipient addresses, subject, HTML/text, headers,
1324
- attachment names/content, credentials and raw provider errors are never copied
1325
- to the persistent event log. If the initial attempt event cannot be persisted,
1326
- the provider is not called.
1327
-
1328
- ## Literals and Translation
1329
-
1330
- Bootstrap receives `literals`. Related utilities:
1331
-
1332
- - `getTranslation`: resolves translatable values.
1333
- - `translateObject`: translates objects with translatable fields.
1334
- - Manager schemas for listing/upserting website literals (not manager UI chrome).
1335
-
1336
- Translatable values use this shape:
1337
-
1338
- ```ts
1339
- {
1340
- _tag: "Translatable",
1341
- en: "Title",
1342
- es: "Titulo"
1343
- }
1344
- ```
1345
-
1346
- ## Permissions
1347
-
1348
- `lib/Permissions` exposes:
1349
-
1350
- - `getPermissionList`: generates a permission list.
1351
- - `hasPermissions`: validates user/role permissions.
1352
- - `mapPermissions`: transforms permissions.
1353
- - `Permission`: public type.
1354
-
1355
- Manager routes apply permissions, ownership, and auth through utilities in `api/utils`.
1356
-
1357
- ## Errors
1358
-
1359
- Application errors live in `lib/errors`:
1360
-
1361
- - `AppError`
1362
- - `throwAppError`
1363
- - `errors`
1364
- - `instanceofAppErrorShape`
1365
-
1366
- Database errors live in `orm/dbService`:
1367
-
1368
- - `DbError`
1369
- - `DbErrorUnknown`
1370
- - `DbErrorNotFound`
1371
- - `DbErrorInvalidData`
1372
- - `DbErrorConflict`
1373
- - `DbErrorSimulatedFailure`
1374
-
1375
- ## Runtime Flow
1376
-
1377
- 1. The app defines content types with `ContentType` and `f`.
1378
- 2. The app calls `rakunBootstrap(options)`.
1379
- 3. The HTTP adapter calls `ensureRakunInitialized()` before serving Rakun routes.
1380
- 4. `ensureRakunInitialized()` configures logger, MongoDB, the persistent event
1381
- log, media, mail, and route syncing.
1382
- 5. Each request creates a `RakunRequestContext`.
1383
- 6. Manager/web operations validate input, run logic, validate output, and return typed contracts.
1384
-
1385
- ## Tests and Development Notes
1386
-
1387
- The package includes targeted tests next to modules (`*.test.ts`), for example ORM, translation, routes, redirects, and populated relations.
1388
-
1389
- Package build:
1390
-
1391
- ```sh
1392
- bun run build --workspace @rakun-kit/core
1393
- ```
1394
-
1395
- The script generates ESM and CJS in `dist/`, adjusting imports and the CJS `package.json` through repo scripts.
1175
+ URLs stable even when the object store is private or only supports expiring
1176
+ presigned URLs. The core handler validates the key under `public/`, forwards
1177
+ byte ranges, preserves object metadata, and applies long-lived caching by
1178
+ default. The storage adapter receives server-side credentials; never expose its
1179
+ presigned storage URL as the public media URL.
1180
+
1181
+ Optimized video uploads use the `ffmpeg-static` peer dependency and produce an MP4 primary object plus MP4 and WebM entries in `sources`. Existing image-only optimize options remain compatible; use `video: { quality: 80 }` to configure video quality explicitly.
1182
+
1183
+ Existing images and videos can be reimported from the media manager with the selected optimization settings. The replacement uses new storage keys and updates the existing `Media` record only after every requested variant has been written, preserving its ID and content references. Preview generation stores a compact `data:image/...` string on `previewUrl` for LQIP use in `@rakun-kit/react` `Image`, instead of writing a separate preview object to storage.
1184
+
1185
+ The `manager.media.replace` mutation accepts a newly uploaded image and swaps it into an existing `Media` record. It preserves the media ID, name, title, alt text, folder, status, and all content references; after the database points to the new storage keys, Rakun removes the previous primary object and generated variants and revalidates directly dependent documents when route revalidation is configured.
1186
+
1187
+ ## Persistent Event Log
1188
+
1189
+ Rakun keeps business/audit events separate from its technical console logger.
1190
+ The event log keeps events immutable and defaults to a MongoDB collection
1191
+ with indexes for time, type, category, outcome, severity, correlation and tags:
1192
+
1193
+ ```ts
1194
+ import { recordEvent, queryEvents } from '@rakun-kit/core'
1195
+
1196
+ await recordEvent({
1197
+ type: 'content.article.published',
1198
+ category: 'content',
1199
+ outcome: 'success',
1200
+ actor: { type: 'manager-user', id: userId },
1201
+ resource: { type: 'Article', id: articleId },
1202
+ correlationId: requestId,
1203
+ tags: ['editorial'],
1204
+ data: {
1205
+ locale: 'es',
1206
+ changedFields: 3,
1207
+ },
1208
+ })
1209
+
1210
+ const page = await queryEvents({
1211
+ categories: ['content'],
1212
+ outcomes: ['success'],
1213
+ from: new Date('2026-01-01T00:00:00.000Z'),
1214
+ limit: 50,
1215
+ })
1216
+ ```
1217
+
1218
+ `data` accepts nested JSON values. Event queries use cursor pagination and can
1219
+ filter by types, categories, severities, outcomes, sources, correlation,
1220
+ required tags and a date range. A custom persistence implementation can be
1221
+ plugged in globally:
1222
+
1223
+ ```ts
1224
+ import type { EventLogAdapter } from '@rakun-kit/core'
1225
+
1226
+ const adapter: EventLogAdapter = {
1227
+ async append(event) {
1228
+ return customStore.append(event)
1229
+ },
1230
+ async query(filters) {
1231
+ return customStore.query(filters)
1232
+ },
1233
+ async deleteBefore(before) {
1234
+ return customStore.deleteBefore(before)
1235
+ },
1236
+ }
1237
+
1238
+ rakunBootstrap({
1239
+ // ...
1240
+ eventLog: { adapter },
1241
+ })
1242
+ ```
1243
+
1244
+ Plugins receive the resolved `eventLog` service in their initialization
1245
+ context. Reading a shared event stream should be protected with the built-in
1246
+ `system.eventLog.read` permission. The built-in `manager.logs.list` operation and the
1247
+ manager Settings → Logs screen both enforce it.
1248
+
1249
+ Retention cleanup is available from the manager Logs screen to users with the
1250
+ built-in `system.eventLog.manage` permission. It deletes events strictly older
1251
+ than the selected cutoff. Custom adapters must implement `deleteBefore` to
1252
+ support this action. The cleanup mutation itself is logged after deletion.
1253
+
1254
+ Failed API operations are persisted automatically as `api.operation.failed`
1255
+ events. This includes expected 4xx application errors and unexpected 5xx
1256
+ failures across the core operation wrapper and the Express, Next.js, and tRPC
1257
+ adapters. Events include the operation, status, method, kind, correlation ID,
1258
+ and authenticated actor when available. Request payloads, application error
1259
+ causes, and raw internal error messages are not persisted.
1260
+
1261
+ Every successful API mutation is also persisted with an operation-specific
1262
+ event type such as `manager.create.succeeded` or
1263
+ `manager.backups.restore.succeeded`. Successful queries are not recorded by
1264
+ default, which keeps the event stream focused on state changes. Mutation
1265
+ events contain operation metadata and actor/correlation context, but never
1266
+ copy the input payload or result.
1267
+
1268
+ ## Mail
1269
+
1270
+ Mail providers receive normalized, already-rendered messages through
1271
+ `MailAdapter`, so neither `core` nor an adapter depends on React or a template
1272
+ engine:
1273
+
1274
+ ```ts
1275
+ import type { MailAdapter } from '@rakun-kit/core'
1276
+
1277
+ const adapter: MailAdapter = {
1278
+ async send(message) {
1279
+ const result = await provider.send(message)
1280
+ return { id: result.id }
1281
+ },
1282
+ }
1283
+
1284
+ rakunBootstrap({
1285
+ // ...
1286
+ mail: {
1287
+ adapter,
1288
+ defaultFrom: 'hello@example.com',
1289
+ defaultReplyTo: 'support@example.com',
1290
+ },
1291
+ })
1292
+ ```
1293
+
1294
+ Send rendered content directly:
1295
+
1296
+ ```ts
1297
+ import { sendMail } from '@rakun-kit/core'
1298
+
1299
+ await sendMail({
1300
+ to: 'ada@example.com',
1301
+ subject: 'Welcome',
1302
+ html: '<p>Hello Ada</p>',
1303
+ text: 'Hello Ada',
1304
+ })
1305
+ ```
1306
+
1307
+ Or create a typed application template registry:
1308
+
1309
+ ```ts
1310
+ import { createMailSender, defineMailTemplate } from '@rakun-kit/core'
1311
+
1312
+ const mail = createMailSender({
1313
+ templates: {
1314
+ welcome: defineMailTemplate<{ name: string }>({
1315
+ subject: ({ name }) => `Welcome, ${name}`,
1316
+ render: ({ name }) => ({
1317
+ html: `<p>Hello ${name}</p>`,
1318
+ text: `Hello ${name}`,
1319
+ }),
1320
+ }),
1321
+ },
1322
+ })
1323
+
1324
+ await mail.send({
1325
+ template: 'welcome',
1326
+ props: { name: 'Ada' },
1327
+ to: 'ada@example.com',
1328
+ })
1329
+ ```
1330
+
1331
+ The common contract supports To/CC/BCC/Reply-To, custom headers and in-memory
1332
+ `Uint8Array` attachments. Sending is immediate; queues, retries and delivery
1333
+ events belong to application infrastructure.
1334
+
1335
+ Every mail sent through a bootstrapped Rakun mail service creates append-only
1336
+ `mail.send.attempted` and `mail.send.succeeded` or `mail.send.failed` events.
1337
+ They share a correlation id and include only operational counts, provider,
1338
+ template and duration. Recipient addresses, subject, HTML/text, headers,
1339
+ attachment names/content, credentials and raw provider errors are never copied
1340
+ to the persistent event log. If the initial attempt event cannot be persisted,
1341
+ the provider is not called.
1342
+
1343
+ ## Literals and Translation
1344
+
1345
+ Bootstrap receives `literals`. Related utilities:
1346
+
1347
+ - `getTranslation`: resolves translatable values.
1348
+ - `translateObject`: translates objects with translatable fields.
1349
+ - Manager schemas for listing/upserting website literals (not manager UI chrome).
1350
+
1351
+ Translatable values use this shape:
1352
+
1353
+ ```ts
1354
+ {
1355
+ _tag: "Translatable",
1356
+ en: "Title",
1357
+ es: "Titulo"
1358
+ }
1359
+ ```
1360
+
1361
+ ## Permissions
1362
+
1363
+ `lib/Permissions` exposes:
1364
+
1365
+ - `getPermissionList`: generates a permission list.
1366
+ - `hasPermissions`: validates user/role permissions.
1367
+ - `mapPermissions`: transforms permissions.
1368
+ - `Permission`: public type.
1369
+
1370
+ Manager routes apply permissions, ownership, and auth through utilities in `api/utils`.
1371
+
1372
+ ## Errors
1373
+
1374
+ Application errors live in `lib/errors`:
1375
+
1376
+ - `AppError`
1377
+ - `throwAppError`
1378
+ - `errors`
1379
+ - `instanceofAppErrorShape`
1380
+
1381
+ Database errors live in `orm/dbService`:
1382
+
1383
+ - `DbError`
1384
+ - `DbErrorUnknown`
1385
+ - `DbErrorNotFound`
1386
+ - `DbErrorInvalidData`
1387
+ - `DbErrorConflict`
1388
+ - `DbErrorSimulatedFailure`
1389
+
1390
+ ## Runtime Flow
1391
+
1392
+ 1. The app defines content types with `ContentType` and `f`.
1393
+ 2. The app calls `rakunBootstrap(options)`.
1394
+ 3. The HTTP adapter calls `ensureRakunInitialized()` before serving Rakun routes.
1395
+ 4. `ensureRakunInitialized()` configures logger, MongoDB, the persistent event
1396
+ log, media, mail, and route syncing.
1397
+ 5. Each request creates a `RakunRequestContext`.
1398
+ 6. Manager/web operations validate input, run logic, validate output, and return typed contracts.
1399
+
1400
+ ## Tests and Development Notes
1401
+
1402
+ The package includes targeted tests next to modules (`*.test.ts`), for example ORM, translation, routes, redirects, and populated relations.
1403
+
1404
+ Package build:
1405
+
1406
+ ```sh
1407
+ bun run build --workspace @rakun-kit/core
1408
+ ```
1409
+
1410
+ The script generates ESM and CJS in `dist/`, adjusting imports and the CJS `package.json` through repo scripts.