convex-cms 0.0.1

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 (379) hide show
  1. package/dist/cli/commands/admin.d.ts +16 -0
  2. package/dist/cli/commands/admin.d.ts.map +1 -0
  3. package/dist/cli/commands/admin.js +88 -0
  4. package/dist/cli/commands/admin.js.map +1 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +18 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/utils/detectConvexUrl.d.ts +13 -0
  10. package/dist/cli/utils/detectConvexUrl.d.ts.map +1 -0
  11. package/dist/cli/utils/detectConvexUrl.js +48 -0
  12. package/dist/cli/utils/detectConvexUrl.js.map +1 -0
  13. package/dist/cli/utils/openBrowser.d.ts +7 -0
  14. package/dist/cli/utils/openBrowser.d.ts.map +1 -0
  15. package/dist/cli/utils/openBrowser.js +17 -0
  16. package/dist/cli/utils/openBrowser.js.map +1 -0
  17. package/dist/client/admin-config.d.ts +126 -0
  18. package/dist/client/admin-config.d.ts.map +1 -0
  19. package/dist/client/admin-config.js +117 -0
  20. package/dist/client/admin-config.js.map +1 -0
  21. package/dist/client/adminApi.d.ts +2273 -0
  22. package/dist/client/adminApi.d.ts.map +1 -0
  23. package/dist/client/adminApi.js +716 -0
  24. package/dist/client/adminApi.js.map +1 -0
  25. package/dist/client/agentTools.d.ts +933 -0
  26. package/dist/client/agentTools.d.ts.map +1 -0
  27. package/dist/client/agentTools.js +1004 -0
  28. package/dist/client/agentTools.js.map +1 -0
  29. package/dist/client/argTypes.d.ts +212 -0
  30. package/dist/client/argTypes.d.ts.map +1 -0
  31. package/dist/client/argTypes.js +5 -0
  32. package/dist/client/argTypes.js.map +1 -0
  33. package/dist/client/field-types.d.ts +55 -0
  34. package/dist/client/field-types.d.ts.map +1 -0
  35. package/dist/client/field-types.js +152 -0
  36. package/dist/client/field-types.js.map +1 -0
  37. package/dist/client/index.d.ts +189 -0
  38. package/dist/client/index.d.ts.map +1 -0
  39. package/dist/client/index.js +668 -0
  40. package/dist/client/index.js.map +1 -0
  41. package/dist/client/queryBuilder.d.ts +765 -0
  42. package/dist/client/queryBuilder.d.ts.map +1 -0
  43. package/dist/client/queryBuilder.js +970 -0
  44. package/dist/client/queryBuilder.js.map +1 -0
  45. package/dist/client/schema/codegen.d.ts +128 -0
  46. package/dist/client/schema/codegen.d.ts.map +1 -0
  47. package/dist/client/schema/codegen.js +318 -0
  48. package/dist/client/schema/codegen.js.map +1 -0
  49. package/dist/client/schema/defineContentType.d.ts +221 -0
  50. package/dist/client/schema/defineContentType.d.ts.map +1 -0
  51. package/dist/client/schema/defineContentType.js +380 -0
  52. package/dist/client/schema/defineContentType.js.map +1 -0
  53. package/dist/client/schema/index.d.ts +85 -0
  54. package/dist/client/schema/index.d.ts.map +1 -0
  55. package/dist/client/schema/index.js +92 -0
  56. package/dist/client/schema/index.js.map +1 -0
  57. package/dist/client/schema/schemaDrift.d.ts +199 -0
  58. package/dist/client/schema/schemaDrift.d.ts.map +1 -0
  59. package/dist/client/schema/schemaDrift.js +340 -0
  60. package/dist/client/schema/schemaDrift.js.map +1 -0
  61. package/dist/client/schema/typedClient.d.ts +401 -0
  62. package/dist/client/schema/typedClient.d.ts.map +1 -0
  63. package/dist/client/schema/typedClient.js +269 -0
  64. package/dist/client/schema/typedClient.js.map +1 -0
  65. package/dist/client/schema/types.d.ts +477 -0
  66. package/dist/client/schema/types.d.ts.map +1 -0
  67. package/dist/client/schema/types.js +39 -0
  68. package/dist/client/schema/types.js.map +1 -0
  69. package/dist/client/types.d.ts +449 -0
  70. package/dist/client/types.d.ts.map +1 -0
  71. package/dist/client/types.js +149 -0
  72. package/dist/client/types.js.map +1 -0
  73. package/dist/client/workflows.d.ts +51 -0
  74. package/dist/client/workflows.d.ts.map +1 -0
  75. package/dist/client/workflows.js +103 -0
  76. package/dist/client/workflows.js.map +1 -0
  77. package/dist/client/wrapper.d.ts +2198 -0
  78. package/dist/client/wrapper.d.ts.map +1 -0
  79. package/dist/client/wrapper.js +2651 -0
  80. package/dist/client/wrapper.js.map +1 -0
  81. package/dist/component/_generated/api.d.ts +124 -0
  82. package/dist/component/_generated/api.d.ts.map +1 -0
  83. package/dist/component/_generated/api.js +31 -0
  84. package/dist/component/_generated/api.js.map +1 -0
  85. package/dist/component/_generated/component.d.ts +4321 -0
  86. package/dist/component/_generated/component.d.ts.map +1 -0
  87. package/dist/component/_generated/component.js +11 -0
  88. package/dist/component/_generated/component.js.map +1 -0
  89. package/dist/component/_generated/dataModel.d.ts +46 -0
  90. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  91. package/dist/component/_generated/dataModel.js +11 -0
  92. package/dist/component/_generated/dataModel.js.map +1 -0
  93. package/dist/component/_generated/server.d.ts +121 -0
  94. package/dist/component/_generated/server.d.ts.map +1 -0
  95. package/dist/component/_generated/server.js +78 -0
  96. package/dist/component/_generated/server.js.map +1 -0
  97. package/dist/component/auditLog.d.ts +410 -0
  98. package/dist/component/auditLog.d.ts.map +1 -0
  99. package/dist/component/auditLog.js +607 -0
  100. package/dist/component/auditLog.js.map +1 -0
  101. package/dist/component/authorization.d.ts +323 -0
  102. package/dist/component/authorization.d.ts.map +1 -0
  103. package/dist/component/authorization.js +464 -0
  104. package/dist/component/authorization.js.map +1 -0
  105. package/dist/component/authorizationHooks.d.ts +184 -0
  106. package/dist/component/authorizationHooks.d.ts.map +1 -0
  107. package/dist/component/authorizationHooks.js +521 -0
  108. package/dist/component/authorizationHooks.js.map +1 -0
  109. package/dist/component/bulkOperations.d.ts +200 -0
  110. package/dist/component/bulkOperations.d.ts.map +1 -0
  111. package/dist/component/bulkOperations.js +568 -0
  112. package/dist/component/bulkOperations.js.map +1 -0
  113. package/dist/component/contentEntries.d.ts +719 -0
  114. package/dist/component/contentEntries.d.ts.map +1 -0
  115. package/dist/component/contentEntries.js +1617 -0
  116. package/dist/component/contentEntries.js.map +1 -0
  117. package/dist/component/contentEntryMutations.d.ts +505 -0
  118. package/dist/component/contentEntryMutations.d.ts.map +1 -0
  119. package/dist/component/contentEntryMutations.js +1009 -0
  120. package/dist/component/contentEntryMutations.js.map +1 -0
  121. package/dist/component/contentEntryValidation.d.ts +115 -0
  122. package/dist/component/contentEntryValidation.d.ts.map +1 -0
  123. package/dist/component/contentEntryValidation.js +546 -0
  124. package/dist/component/contentEntryValidation.js.map +1 -0
  125. package/dist/component/contentLock.d.ts +328 -0
  126. package/dist/component/contentLock.d.ts.map +1 -0
  127. package/dist/component/contentLock.js +471 -0
  128. package/dist/component/contentLock.js.map +1 -0
  129. package/dist/component/contentTypeMigration.d.ts +411 -0
  130. package/dist/component/contentTypeMigration.d.ts.map +1 -0
  131. package/dist/component/contentTypeMigration.js +805 -0
  132. package/dist/component/contentTypeMigration.js.map +1 -0
  133. package/dist/component/contentTypeMutations.d.ts +975 -0
  134. package/dist/component/contentTypeMutations.d.ts.map +1 -0
  135. package/dist/component/contentTypeMutations.js +768 -0
  136. package/dist/component/contentTypeMutations.js.map +1 -0
  137. package/dist/component/contentTypes.d.ts +538 -0
  138. package/dist/component/contentTypes.d.ts.map +1 -0
  139. package/dist/component/contentTypes.js +304 -0
  140. package/dist/component/contentTypes.js.map +1 -0
  141. package/dist/component/convex.config.d.ts +42 -0
  142. package/dist/component/convex.config.d.ts.map +1 -0
  143. package/dist/component/convex.config.js +43 -0
  144. package/dist/component/convex.config.js.map +1 -0
  145. package/dist/component/documentTypes.d.ts +186 -0
  146. package/dist/component/documentTypes.d.ts.map +1 -0
  147. package/dist/component/documentTypes.js +23 -0
  148. package/dist/component/documentTypes.js.map +1 -0
  149. package/dist/component/eventEmitter.d.ts +281 -0
  150. package/dist/component/eventEmitter.d.ts.map +1 -0
  151. package/dist/component/eventEmitter.js +300 -0
  152. package/dist/component/eventEmitter.js.map +1 -0
  153. package/dist/component/exportImport.d.ts +1120 -0
  154. package/dist/component/exportImport.d.ts.map +1 -0
  155. package/dist/component/exportImport.js +931 -0
  156. package/dist/component/exportImport.js.map +1 -0
  157. package/dist/component/index.d.ts +28 -0
  158. package/dist/component/index.d.ts.map +1 -0
  159. package/dist/component/index.js +142 -0
  160. package/dist/component/index.js.map +1 -0
  161. package/dist/component/lib/deepReferenceResolver.d.ts +252 -0
  162. package/dist/component/lib/deepReferenceResolver.d.ts.map +1 -0
  163. package/dist/component/lib/deepReferenceResolver.js +601 -0
  164. package/dist/component/lib/deepReferenceResolver.js.map +1 -0
  165. package/dist/component/lib/errors.d.ts +306 -0
  166. package/dist/component/lib/errors.d.ts.map +1 -0
  167. package/dist/component/lib/errors.js +407 -0
  168. package/dist/component/lib/errors.js.map +1 -0
  169. package/dist/component/lib/index.d.ts +10 -0
  170. package/dist/component/lib/index.d.ts.map +1 -0
  171. package/dist/component/lib/index.js +33 -0
  172. package/dist/component/lib/index.js.map +1 -0
  173. package/dist/component/lib/mediaReferenceResolver.d.ts +217 -0
  174. package/dist/component/lib/mediaReferenceResolver.d.ts.map +1 -0
  175. package/dist/component/lib/mediaReferenceResolver.js +326 -0
  176. package/dist/component/lib/mediaReferenceResolver.js.map +1 -0
  177. package/dist/component/lib/metadataExtractor.d.ts +245 -0
  178. package/dist/component/lib/metadataExtractor.d.ts.map +1 -0
  179. package/dist/component/lib/metadataExtractor.js +548 -0
  180. package/dist/component/lib/metadataExtractor.js.map +1 -0
  181. package/dist/component/lib/mutationAuth.d.ts +95 -0
  182. package/dist/component/lib/mutationAuth.d.ts.map +1 -0
  183. package/dist/component/lib/mutationAuth.js +146 -0
  184. package/dist/component/lib/mutationAuth.js.map +1 -0
  185. package/dist/component/lib/queries.d.ts +17 -0
  186. package/dist/component/lib/queries.d.ts.map +1 -0
  187. package/dist/component/lib/queries.js +49 -0
  188. package/dist/component/lib/queries.js.map +1 -0
  189. package/dist/component/lib/ragContentChunker.d.ts +423 -0
  190. package/dist/component/lib/ragContentChunker.d.ts.map +1 -0
  191. package/dist/component/lib/ragContentChunker.js +897 -0
  192. package/dist/component/lib/ragContentChunker.js.map +1 -0
  193. package/dist/component/lib/referenceResolver.d.ts +175 -0
  194. package/dist/component/lib/referenceResolver.d.ts.map +1 -0
  195. package/dist/component/lib/referenceResolver.js +293 -0
  196. package/dist/component/lib/referenceResolver.js.map +1 -0
  197. package/dist/component/lib/slugGenerator.d.ts +71 -0
  198. package/dist/component/lib/slugGenerator.d.ts.map +1 -0
  199. package/dist/component/lib/slugGenerator.js +207 -0
  200. package/dist/component/lib/slugGenerator.js.map +1 -0
  201. package/dist/component/lib/slugUniqueness.d.ts +131 -0
  202. package/dist/component/lib/slugUniqueness.d.ts.map +1 -0
  203. package/dist/component/lib/slugUniqueness.js +229 -0
  204. package/dist/component/lib/slugUniqueness.js.map +1 -0
  205. package/dist/component/lib/softDelete.d.ts +18 -0
  206. package/dist/component/lib/softDelete.d.ts.map +1 -0
  207. package/dist/component/lib/softDelete.js +29 -0
  208. package/dist/component/lib/softDelete.js.map +1 -0
  209. package/dist/component/localeFallbackChain.d.ts +410 -0
  210. package/dist/component/localeFallbackChain.d.ts.map +1 -0
  211. package/dist/component/localeFallbackChain.js +467 -0
  212. package/dist/component/localeFallbackChain.js.map +1 -0
  213. package/dist/component/localeFields.d.ts +508 -0
  214. package/dist/component/localeFields.d.ts.map +1 -0
  215. package/dist/component/localeFields.js +592 -0
  216. package/dist/component/localeFields.js.map +1 -0
  217. package/dist/component/mediaAssetMutations.d.ts +235 -0
  218. package/dist/component/mediaAssetMutations.d.ts.map +1 -0
  219. package/dist/component/mediaAssetMutations.js +558 -0
  220. package/dist/component/mediaAssetMutations.js.map +1 -0
  221. package/dist/component/mediaAssets.d.ts +168 -0
  222. package/dist/component/mediaAssets.d.ts.map +1 -0
  223. package/dist/component/mediaAssets.js +618 -0
  224. package/dist/component/mediaAssets.js.map +1 -0
  225. package/dist/component/mediaFolderMutations.d.ts +642 -0
  226. package/dist/component/mediaFolderMutations.d.ts.map +1 -0
  227. package/dist/component/mediaFolderMutations.js +849 -0
  228. package/dist/component/mediaFolderMutations.js.map +1 -0
  229. package/dist/component/mediaUploadMutations.d.ts +136 -0
  230. package/dist/component/mediaUploadMutations.d.ts.map +1 -0
  231. package/dist/component/mediaUploadMutations.js +205 -0
  232. package/dist/component/mediaUploadMutations.js.map +1 -0
  233. package/dist/component/mediaVariantMutations.d.ts +468 -0
  234. package/dist/component/mediaVariantMutations.d.ts.map +1 -0
  235. package/dist/component/mediaVariantMutations.js +737 -0
  236. package/dist/component/mediaVariantMutations.js.map +1 -0
  237. package/dist/component/mediaVariants.d.ts +525 -0
  238. package/dist/component/mediaVariants.d.ts.map +1 -0
  239. package/dist/component/mediaVariants.js +661 -0
  240. package/dist/component/mediaVariants.js.map +1 -0
  241. package/dist/component/ragContentIndexer.d.ts +595 -0
  242. package/dist/component/ragContentIndexer.d.ts.map +1 -0
  243. package/dist/component/ragContentIndexer.js +794 -0
  244. package/dist/component/ragContentIndexer.js.map +1 -0
  245. package/dist/component/rateLimitHooks.d.ts +266 -0
  246. package/dist/component/rateLimitHooks.d.ts.map +1 -0
  247. package/dist/component/rateLimitHooks.js +412 -0
  248. package/dist/component/rateLimitHooks.js.map +1 -0
  249. package/dist/component/roles.d.ts +649 -0
  250. package/dist/component/roles.d.ts.map +1 -0
  251. package/dist/component/roles.js +884 -0
  252. package/dist/component/roles.js.map +1 -0
  253. package/dist/component/scheduledPublish.d.ts +182 -0
  254. package/dist/component/scheduledPublish.d.ts.map +1 -0
  255. package/dist/component/scheduledPublish.js +304 -0
  256. package/dist/component/scheduledPublish.js.map +1 -0
  257. package/dist/component/schema.d.ts +4114 -0
  258. package/dist/component/schema.d.ts.map +1 -0
  259. package/dist/component/schema.js +469 -0
  260. package/dist/component/schema.js.map +1 -0
  261. package/dist/component/taxonomies.d.ts +476 -0
  262. package/dist/component/taxonomies.d.ts.map +1 -0
  263. package/dist/component/taxonomies.js +785 -0
  264. package/dist/component/taxonomies.js.map +1 -0
  265. package/dist/component/taxonomyMutations.d.ts +206 -0
  266. package/dist/component/taxonomyMutations.d.ts.map +1 -0
  267. package/dist/component/taxonomyMutations.js +1001 -0
  268. package/dist/component/taxonomyMutations.js.map +1 -0
  269. package/dist/component/trash.d.ts +265 -0
  270. package/dist/component/trash.d.ts.map +1 -0
  271. package/dist/component/trash.js +621 -0
  272. package/dist/component/trash.js.map +1 -0
  273. package/dist/component/types.d.ts +4 -0
  274. package/dist/component/types.d.ts.map +1 -0
  275. package/dist/component/types.js +2 -0
  276. package/dist/component/types.js.map +1 -0
  277. package/dist/component/userContext.d.ts +508 -0
  278. package/dist/component/userContext.d.ts.map +1 -0
  279. package/dist/component/userContext.js +615 -0
  280. package/dist/component/userContext.js.map +1 -0
  281. package/dist/component/validation.d.ts +387 -0
  282. package/dist/component/validation.d.ts.map +1 -0
  283. package/dist/component/validation.js +1052 -0
  284. package/dist/component/validation.js.map +1 -0
  285. package/dist/component/validators.d.ts +4645 -0
  286. package/dist/component/validators.d.ts.map +1 -0
  287. package/dist/component/validators.js +641 -0
  288. package/dist/component/validators.js.map +1 -0
  289. package/dist/component/versionMutations.d.ts +216 -0
  290. package/dist/component/versionMutations.d.ts.map +1 -0
  291. package/dist/component/versionMutations.js +321 -0
  292. package/dist/component/versionMutations.js.map +1 -0
  293. package/dist/component/webhookTrigger.d.ts +770 -0
  294. package/dist/component/webhookTrigger.d.ts.map +1 -0
  295. package/dist/component/webhookTrigger.js +1413 -0
  296. package/dist/component/webhookTrigger.js.map +1 -0
  297. package/dist/react/index.d.ts +316 -0
  298. package/dist/react/index.d.ts.map +1 -0
  299. package/dist/react/index.js +558 -0
  300. package/dist/react/index.js.map +1 -0
  301. package/dist/test.d.ts +2230 -0
  302. package/dist/test.d.ts.map +1 -0
  303. package/dist/test.js +1107 -0
  304. package/dist/test.js.map +1 -0
  305. package/package.json +95 -0
  306. package/src/cli/commands/admin.ts +104 -0
  307. package/src/cli/index.ts +21 -0
  308. package/src/cli/utils/detectConvexUrl.ts +54 -0
  309. package/src/cli/utils/openBrowser.ts +16 -0
  310. package/src/client/admin-config.ts +138 -0
  311. package/src/client/adminApi.ts +942 -0
  312. package/src/client/agentTools.ts +1311 -0
  313. package/src/client/argTypes.ts +316 -0
  314. package/src/client/field-types.ts +187 -0
  315. package/src/client/index.ts +1301 -0
  316. package/src/client/queryBuilder.ts +1100 -0
  317. package/src/client/schema/codegen.ts +500 -0
  318. package/src/client/schema/defineContentType.ts +501 -0
  319. package/src/client/schema/index.ts +169 -0
  320. package/src/client/schema/schemaDrift.ts +574 -0
  321. package/src/client/schema/typedClient.ts +688 -0
  322. package/src/client/schema/types.ts +666 -0
  323. package/src/client/types.ts +723 -0
  324. package/src/client/workflows.ts +141 -0
  325. package/src/client/wrapper.ts +4304 -0
  326. package/src/component/_generated/api.ts +140 -0
  327. package/src/component/_generated/component.ts +5029 -0
  328. package/src/component/_generated/dataModel.ts +60 -0
  329. package/src/component/_generated/server.ts +156 -0
  330. package/src/component/authorization.ts +647 -0
  331. package/src/component/authorizationHooks.ts +668 -0
  332. package/src/component/bulkOperations.ts +687 -0
  333. package/src/component/contentEntries.ts +1976 -0
  334. package/src/component/contentEntryMutations.ts +1223 -0
  335. package/src/component/contentEntryValidation.ts +707 -0
  336. package/src/component/contentLock.ts +550 -0
  337. package/src/component/contentTypeMigration.ts +1064 -0
  338. package/src/component/contentTypeMutations.ts +969 -0
  339. package/src/component/contentTypes.ts +346 -0
  340. package/src/component/convex.config.ts +44 -0
  341. package/src/component/documentTypes.ts +240 -0
  342. package/src/component/eventEmitter.ts +485 -0
  343. package/src/component/exportImport.ts +1169 -0
  344. package/src/component/index.ts +491 -0
  345. package/src/component/lib/deepReferenceResolver.ts +999 -0
  346. package/src/component/lib/errors.ts +816 -0
  347. package/src/component/lib/index.ts +145 -0
  348. package/src/component/lib/mediaReferenceResolver.ts +495 -0
  349. package/src/component/lib/metadataExtractor.ts +792 -0
  350. package/src/component/lib/mutationAuth.ts +199 -0
  351. package/src/component/lib/queries.ts +79 -0
  352. package/src/component/lib/ragContentChunker.ts +1371 -0
  353. package/src/component/lib/referenceResolver.ts +430 -0
  354. package/src/component/lib/slugGenerator.ts +262 -0
  355. package/src/component/lib/slugUniqueness.ts +333 -0
  356. package/src/component/lib/softDelete.ts +44 -0
  357. package/src/component/localeFallbackChain.ts +673 -0
  358. package/src/component/localeFields.ts +896 -0
  359. package/src/component/mediaAssetMutations.ts +725 -0
  360. package/src/component/mediaAssets.ts +932 -0
  361. package/src/component/mediaFolderMutations.ts +1046 -0
  362. package/src/component/mediaUploadMutations.ts +224 -0
  363. package/src/component/mediaVariantMutations.ts +900 -0
  364. package/src/component/mediaVariants.ts +793 -0
  365. package/src/component/ragContentIndexer.ts +1067 -0
  366. package/src/component/rateLimitHooks.ts +572 -0
  367. package/src/component/roles.ts +1360 -0
  368. package/src/component/scheduledPublish.ts +358 -0
  369. package/src/component/schema.ts +617 -0
  370. package/src/component/taxonomies.ts +949 -0
  371. package/src/component/taxonomyMutations.ts +1210 -0
  372. package/src/component/trash.ts +724 -0
  373. package/src/component/userContext.ts +898 -0
  374. package/src/component/validation.ts +1388 -0
  375. package/src/component/validators.ts +949 -0
  376. package/src/component/versionMutations.ts +392 -0
  377. package/src/component/webhookTrigger.ts +1922 -0
  378. package/src/react/index.ts +898 -0
  379. package/src/test.ts +1580 -0
@@ -0,0 +1,884 @@
1
+ /**
2
+ * RBAC Default Roles Configuration
3
+ *
4
+ * This module defines the default role configurations for the CMS:
5
+ * - admin: Full access to all CMS features
6
+ * - editor: Can manage all content and media, but not settings
7
+ * - author: Can create and manage own content
8
+ * - viewer: Read-only access to published content
9
+ *
10
+ * Roles are exported as constants for easy customization. Developers can
11
+ * extend or override these defaults using the custom roles feature.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { DEFAULT_ROLES, hasPermission, type RoleName } from '@convex-cms/core';
16
+ *
17
+ * // Check if a role has a specific permission
18
+ * if (hasPermission('editor', { resource: 'contentEntries', action: 'update' })) {
19
+ * // Allow the action
20
+ * }
21
+ *
22
+ * // Get all permissions for a role
23
+ * const adminPerms = getRolePermissions('admin');
24
+ * ```
25
+ */
26
+ import { v } from "convex/values";
27
+ // =============================================================================
28
+ // Role Name Constants
29
+ // =============================================================================
30
+ /**
31
+ * All built-in role names in the CMS.
32
+ * Custom roles can be added by developers, but these are always available.
33
+ */
34
+ export const roleNames = ["admin", "editor", "author", "viewer"];
35
+ /**
36
+ * Convex validator for role names.
37
+ * Use this in function arguments to validate role input.
38
+ */
39
+ export const roleNameValidator = v.union(v.literal("admin"), v.literal("editor"), v.literal("author"), v.literal("viewer"));
40
+ // =============================================================================
41
+ // Resource and Action Constants
42
+ // =============================================================================
43
+ /**
44
+ * All resources that can be protected by RBAC.
45
+ */
46
+ export const resources = [
47
+ "contentTypes",
48
+ "contentEntries",
49
+ "mediaItems",
50
+ "settings",
51
+ ];
52
+ /**
53
+ * Convex validator for resources.
54
+ */
55
+ export const resourceValidator = v.union(v.literal("contentTypes"), v.literal("contentEntries"), v.literal("mediaItems"), v.literal("settings"));
56
+ /**
57
+ * All actions that can be performed on resources.
58
+ */
59
+ export const actions = [
60
+ "create",
61
+ "read",
62
+ "update",
63
+ "delete",
64
+ "publish",
65
+ "unpublish",
66
+ "restore",
67
+ "manage", // Special action for full control (e.g., settings)
68
+ "move", // Move items between folders (media)
69
+ ];
70
+ /**
71
+ * Convex validator for actions.
72
+ */
73
+ export const actionValidator = v.union(v.literal("create"), v.literal("read"), v.literal("update"), v.literal("delete"), v.literal("publish"), v.literal("unpublish"), v.literal("restore"), v.literal("manage"), v.literal("move"));
74
+ /**
75
+ * Convex validator for a permission object.
76
+ */
77
+ export const permissionValidator = v.object({
78
+ resource: resourceValidator,
79
+ action: actionValidator,
80
+ scope: v.optional(v.union(v.literal("all"), v.literal("own"))),
81
+ });
82
+ // =============================================================================
83
+ // Permission Factory Helpers
84
+ // =============================================================================
85
+ /**
86
+ * Helper to create a full CRUD permission set for a resource.
87
+ */
88
+ function fullCrud(resource, scope = "all") {
89
+ return [
90
+ { resource, action: "create", scope },
91
+ { resource, action: "read", scope },
92
+ { resource, action: "update", scope },
93
+ { resource, action: "delete", scope },
94
+ ];
95
+ }
96
+ /**
97
+ * Helper to create read-only permission for a resource.
98
+ */
99
+ function readOnly(resource, scope = "all") {
100
+ return [{ resource, action: "read", scope }];
101
+ }
102
+ /**
103
+ * Helper to create publish permissions for content.
104
+ */
105
+ function publishPermissions(scope = "all") {
106
+ return [
107
+ { resource: "contentEntries", action: "publish", scope },
108
+ { resource: "contentEntries", action: "unpublish", scope },
109
+ ];
110
+ }
111
+ // =============================================================================
112
+ // Default Role Definitions
113
+ // =============================================================================
114
+ /**
115
+ * Admin role - Full access to all CMS features.
116
+ *
117
+ * Admins can:
118
+ * - Create, read, update, and delete all content types
119
+ * - Manage all content entries regardless of author
120
+ * - Publish and unpublish any content
121
+ * - Manage all media assets and folders
122
+ * - Access and modify CMS settings
123
+ */
124
+ export const ADMIN_ROLE = {
125
+ name: "admin",
126
+ displayName: "Administrator",
127
+ description: "Full access to all CMS features including settings and content type management",
128
+ isSystem: true,
129
+ permissions: [
130
+ // Content types - full management
131
+ ...fullCrud("contentTypes"),
132
+ // Content entries - full CRUD + publish
133
+ ...fullCrud("contentEntries"),
134
+ ...publishPermissions(),
135
+ { resource: "contentEntries", action: "restore" },
136
+ // Media - full management
137
+ ...fullCrud("mediaItems"),
138
+ // Settings - full access
139
+ { resource: "settings", action: "manage" },
140
+ ...readOnly("settings"),
141
+ ],
142
+ };
143
+ /**
144
+ * Editor role - Can manage all content and media, but not settings or content types.
145
+ *
146
+ * Editors can:
147
+ * - Read content type definitions
148
+ * - Create, read, update, and delete all content entries
149
+ * - Publish and unpublish any content
150
+ * - Manage all media assets and folders
151
+ * - Cannot modify CMS settings or content type schemas
152
+ */
153
+ export const EDITOR_ROLE = {
154
+ name: "editor",
155
+ displayName: "Editor",
156
+ description: "Can manage all content and media, but cannot modify settings or content types",
157
+ isSystem: true,
158
+ permissions: [
159
+ // Content types - read only
160
+ ...readOnly("contentTypes"),
161
+ // Content entries - full CRUD + publish
162
+ ...fullCrud("contentEntries"),
163
+ ...publishPermissions(),
164
+ { resource: "contentEntries", action: "restore" },
165
+ // Media - full management
166
+ ...fullCrud("mediaItems"),
167
+ ],
168
+ };
169
+ /**
170
+ * Author role - Can create and manage own content.
171
+ *
172
+ * Authors can:
173
+ * - Read content type definitions
174
+ * - Create content entries
175
+ * - Read, update, and delete their own content entries
176
+ * - Publish and unpublish their own content (subject to workflow settings)
177
+ * - Upload and manage their own media assets
178
+ * - Read all media (for embedding in content)
179
+ * - Cannot manage other users' content or CMS settings
180
+ */
181
+ export const AUTHOR_ROLE = {
182
+ name: "author",
183
+ displayName: "Author",
184
+ description: "Can create and manage own content and media",
185
+ isSystem: true,
186
+ permissions: [
187
+ // Content types - read only
188
+ ...readOnly("contentTypes"),
189
+ // Content entries - own content only
190
+ { resource: "contentEntries", action: "create" },
191
+ { resource: "contentEntries", action: "read", scope: "own" },
192
+ { resource: "contentEntries", action: "update", scope: "own" },
193
+ { resource: "contentEntries", action: "delete", scope: "own" },
194
+ // Authors can publish/unpublish their own content
195
+ { resource: "contentEntries", action: "publish", scope: "own" },
196
+ { resource: "contentEntries", action: "unpublish", scope: "own" },
197
+ // Media - can create and manage own, read all (for embedding)
198
+ { resource: "mediaItems", action: "create" },
199
+ { resource: "mediaItems", action: "read", scope: "all" }, // Can read all for embedding
200
+ { resource: "mediaItems", action: "update", scope: "own" },
201
+ { resource: "mediaItems", action: "delete", scope: "own" },
202
+ ],
203
+ };
204
+ /**
205
+ * Viewer role - Read-only access to published content.
206
+ *
207
+ * Viewers can:
208
+ * - Read content type definitions
209
+ * - Read published content entries only
210
+ * - View media assets
211
+ * - Cannot create, update, delete, or publish any content
212
+ */
213
+ export const VIEWER_ROLE = {
214
+ name: "viewer",
215
+ displayName: "Viewer",
216
+ description: "Read-only access to published content and media",
217
+ isSystem: true,
218
+ permissions: [
219
+ // Content types - read only
220
+ ...readOnly("contentTypes"),
221
+ // Content entries - read published only (scope: "all" means all published)
222
+ ...readOnly("contentEntries"),
223
+ // Media - read only
224
+ ...readOnly("mediaItems"),
225
+ ],
226
+ };
227
+ // =============================================================================
228
+ // Default Roles Collection
229
+ // =============================================================================
230
+ /**
231
+ * All default roles indexed by role name.
232
+ * Use this to look up role definitions or iterate over all roles.
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * // Get the admin role definition
237
+ * const adminDef = DEFAULT_ROLES.admin;
238
+ *
239
+ * // Iterate over all roles
240
+ * for (const [name, role] of Object.entries(DEFAULT_ROLES)) {
241
+ * console.log(`${name}: ${role.description}`);
242
+ * }
243
+ * ```
244
+ */
245
+ export const DEFAULT_ROLES = {
246
+ admin: ADMIN_ROLE,
247
+ editor: EDITOR_ROLE,
248
+ author: AUTHOR_ROLE,
249
+ viewer: VIEWER_ROLE,
250
+ };
251
+ /**
252
+ * Array of all default role definitions.
253
+ * Useful for UI rendering or iterating over roles.
254
+ */
255
+ export const DEFAULT_ROLES_LIST = Object.values(DEFAULT_ROLES);
256
+ // =============================================================================
257
+ // Permission Check Utilities
258
+ // =============================================================================
259
+ /**
260
+ * Check if a permission matches a requested permission.
261
+ * Handles scope matching (own scope only matches if requested scope is also own).
262
+ *
263
+ * @param granted - The permission that was granted to the role
264
+ * @param requested - The permission being requested
265
+ * @returns True if the granted permission satisfies the requested permission
266
+ */
267
+ export function permissionMatches(granted, requested) {
268
+ // Resource and action must match
269
+ if (granted.resource !== requested.resource ||
270
+ granted.action !== requested.action) {
271
+ return false;
272
+ }
273
+ // Scope matching:
274
+ // - If granted scope is "all" (or undefined), it covers both "all" and "own" requests
275
+ // - If granted scope is "own", it only covers "own" requests
276
+ const grantedScope = granted.scope ?? "all";
277
+ const requestedScope = requested.scope ?? "all";
278
+ if (grantedScope === "all") {
279
+ return true; // "all" scope covers everything
280
+ }
281
+ // "own" scope only matches "own" requests
282
+ return requestedScope === "own";
283
+ }
284
+ /**
285
+ * Check if a role has a specific permission.
286
+ *
287
+ * @param roleName - The name of the role to check
288
+ * @param permission - The permission to check for (resource + action + optional scope)
289
+ * @param customRoles - Optional custom roles to check in addition to defaults
290
+ * @returns True if the role has the permission
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // Check if editor can update content entries
295
+ * hasPermission('editor', { resource: 'contentEntries', action: 'update' }); // true
296
+ *
297
+ * // Check if author can publish their own content
298
+ * hasPermission('author', { resource: 'contentEntries', action: 'publish', scope: 'own' }); // true
299
+ *
300
+ * // Check if viewer can update content
301
+ * hasPermission('viewer', { resource: 'contentEntries', action: 'update' }); // false
302
+ * ```
303
+ */
304
+ export function hasPermission(roleName, permission, customRoles) {
305
+ // Look up role in default roles first, then custom roles
306
+ const role = DEFAULT_ROLES[roleName] ?? customRoles?.[roleName];
307
+ if (!role) {
308
+ return false; // Unknown role has no permissions
309
+ }
310
+ // Check if any granted permission matches the requested permission
311
+ return role.permissions.some((p) => permissionMatches(p, permission));
312
+ }
313
+ /**
314
+ * Get all permissions for a role.
315
+ *
316
+ * @param roleName - The name of the role
317
+ * @param customRoles - Optional custom roles to check in addition to defaults
318
+ * @returns Array of permissions, or empty array if role not found
319
+ *
320
+ * @example
321
+ * ```typescript
322
+ * const editorPerms = getRolePermissions('editor');
323
+ * console.log(editorPerms.length); // Number of permissions
324
+ * ```
325
+ */
326
+ export function getRolePermissions(roleName, customRoles) {
327
+ const role = DEFAULT_ROLES[roleName] ?? customRoles?.[roleName];
328
+ return role?.permissions ?? [];
329
+ }
330
+ /**
331
+ * Get the role definition for a role name.
332
+ *
333
+ * @param roleName - The name of the role
334
+ * @param customRoles - Optional custom roles to check in addition to defaults
335
+ * @returns The role definition, or undefined if not found
336
+ */
337
+ export function getRole(roleName, customRoles) {
338
+ return DEFAULT_ROLES[roleName] ?? customRoles?.[roleName];
339
+ }
340
+ /**
341
+ * Check if a role name is a valid built-in role.
342
+ *
343
+ * @param name - The role name to check
344
+ * @returns True if it's a valid built-in role name
345
+ */
346
+ export function isBuiltInRole(name) {
347
+ return roleNames.includes(name);
348
+ }
349
+ /**
350
+ * Get all permissions for a specific resource across a role.
351
+ *
352
+ * @param roleName - The name of the role
353
+ * @param resource - The resource to filter by
354
+ * @param customRoles - Optional custom roles to check in addition to defaults
355
+ * @returns Array of permissions for the specified resource
356
+ *
357
+ * @example
358
+ * ```typescript
359
+ * // Get all content entry permissions for editor
360
+ * const contentPerms = getResourcePermissions('editor', 'contentEntries');
361
+ * ```
362
+ */
363
+ export function getResourcePermissions(roleName, resource, customRoles) {
364
+ return getRolePermissions(roleName, customRoles).filter((p) => p.resource === resource);
365
+ }
366
+ /**
367
+ * Check if a role can perform any action on a resource.
368
+ *
369
+ * @param roleName - The name of the role
370
+ * @param resource - The resource to check
371
+ * @param customRoles - Optional custom roles to check in addition to defaults
372
+ * @returns True if the role h permission on the resource
373
+ */
374
+ export function canAccessResource(roleName, resource, customRoles) {
375
+ return getResourcePermissions(roleName, resource, customRoles).length > 0;
376
+ }
377
+ // =============================================================================
378
+ // Custom Role Factory Functions
379
+ // =============================================================================
380
+ /**
381
+ * Creates a new custom role from configuration.
382
+ *
383
+ * @param config - The custom role configuration
384
+ * @returns A role definition ready to use with the RBAC system
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * const blogAuthor = createCustomRole({
389
+ * name: "blog-author",
390
+ * displayName: "Blog Author",
391
+ * description: "Can create and manage blog posts",
392
+ * permissions: [
393
+ * { resource: "contentTypes", action: "read" },
394
+ * { resource: "contentEntries", action: "create", contentTypes: ["blog_post"] },
395
+ * { resource: "contentEntries", action: "read", scope: "own", contentTypes: ["blog_post"] },
396
+ * { resource: "contentEntries", action: "update", scope: "own", contentTypes: ["blog_post"] },
397
+ * { resource: "contentEntries", action: "delete", scope: "own", contentTypes: ["blog_post"] },
398
+ * { resource: "mediaItems", action: "create" },
399
+ * { resource: "mediaItems", action: "read" },
400
+ * ],
401
+ * });
402
+ * ```
403
+ */
404
+ export function createCustomRole(config) {
405
+ // Validate the configuration
406
+ if (!config.name || config.name.trim() === "") {
407
+ throw new Error("Custom role name is required");
408
+ }
409
+ if (isBuiltInRole(config.name)) {
410
+ throw new Error(`Cannot create custom role with built-in role name '${config.name}'. ` +
411
+ "Use extendRole() to extend a built-in role, or choose a different name.");
412
+ }
413
+ return {
414
+ name: config.name,
415
+ displayName: config.displayName,
416
+ description: config.description,
417
+ permissions: config.permissions,
418
+ isSystem: config.isSystem ?? false,
419
+ };
420
+ }
421
+ /**
422
+ * Extends an existing role with additional or removed permissions.
423
+ *
424
+ * This function creates a new role based on an existing one, allowing you to:
425
+ * - Add new permissions
426
+ * - Remove existing permissions
427
+ * - Restrict all contentEntries permissions to specific content types
428
+ *
429
+ * @param config - The extend role configuration
430
+ * @param customRoles - Optional existing custom roles to look up the base role from
431
+ * @returns A new role definition with the modified permissions
432
+ *
433
+ * @example
434
+ * ```typescript
435
+ * // Create a senior author who can publish their own content
436
+ * const seniorAuthor = extendRole({
437
+ * name: "senior-author",
438
+ * displayName: "Senior Author",
439
+ * description: "Author with publishing rights",
440
+ * extends: "author",
441
+ * addPermissions: [
442
+ * { resource: "contentEntries", action: "publish", scope: "own" },
443
+ * { resource: "contentEntries", action: "unpublish", scope: "own" },
444
+ * ],
445
+ * });
446
+ *
447
+ * // Create a blog-only editor
448
+ * const blogEditor = extendRole({
449
+ * name: "blog-editor",
450
+ * displayName: "Blog Editor",
451
+ * description: "Can only edit blog content",
452
+ * extends: "editor",
453
+ * restrictToContentTypes: ["blog_post", "blog_category"],
454
+ * });
455
+ * ```
456
+ */
457
+ export function extendRole(config, customRoles) {
458
+ // Validate the configuration
459
+ if (!config.name || config.name.trim() === "") {
460
+ throw new Error("Extended role name is required");
461
+ }
462
+ if (config.name === config.extends) {
463
+ throw new Error("Extended role name must be different from the base role name");
464
+ }
465
+ // Get the base role
466
+ const baseRole = getRole(config.extends, customRoles);
467
+ if (!baseRole) {
468
+ throw new Error(`Cannot extend unknown role '${config.extends}'. ` +
469
+ "Ensure the role exists as a built-in role or is defined in customRoles.");
470
+ }
471
+ // Start with base permissions
472
+ let permissions = [...baseRole.permissions];
473
+ // Remove specified permissions
474
+ if (config.removePermissions && config.removePermissions.length > 0) {
475
+ permissions = permissions.filter((p) => {
476
+ return !config.removePermissions.some((r) => r.resource === p.resource && r.action === p.action);
477
+ });
478
+ }
479
+ // Add new permissions
480
+ if (config.addPermissions && config.addPermissions.length > 0) {
481
+ permissions = [...permissions, ...config.addPermissions];
482
+ }
483
+ // Apply content type restrictions to all contentEntries permissions
484
+ if (config.restrictToContentTypes &&
485
+ config.restrictToContentTypes.length > 0) {
486
+ permissions = permissions.map((p) => {
487
+ if (p.resource === "contentEntries") {
488
+ return {
489
+ ...p,
490
+ contentTypes: config.restrictToContentTypes,
491
+ };
492
+ }
493
+ return p;
494
+ });
495
+ }
496
+ return {
497
+ name: config.name,
498
+ displayName: config.displayName,
499
+ description: config.description,
500
+ permissions,
501
+ isSystem: config.isSystem ?? false,
502
+ extendsRole: config.extends,
503
+ };
504
+ }
505
+ /**
506
+ * Merges custom roles with the default roles.
507
+ *
508
+ * Creates a combined role registry that includes both default and custom roles.
509
+ * Custom roles do NOT override default roles - they exist alongside them.
510
+ *
511
+ * @param customRoles - Array of custom role definitions
512
+ * @returns A record of all roles (default + custom)
513
+ *
514
+ * @example
515
+ * ```typescript
516
+ * const blogAuthor = createCustomRole({...});
517
+ * const seniorAuthor = extendRole({...});
518
+ *
519
+ * const allRoles = mergeRolesWithDefaults([blogAuthor, seniorAuthor]);
520
+ * // allRoles contains: admin, editor, author, viewer, blog-author, senior-author
521
+ * ```
522
+ */
523
+ export function mergeRolesWithDefaults(customRoles) {
524
+ const result = {
525
+ ...DEFAULT_ROLES,
526
+ };
527
+ for (const role of customRoles) {
528
+ if (isBuiltInRole(role.name)) {
529
+ console.warn(`Warning: Custom role '${role.name}' has the same name as a built-in role. ` +
530
+ "The built-in role will take precedence.");
531
+ continue;
532
+ }
533
+ result[role.name] = role;
534
+ }
535
+ return result;
536
+ }
537
+ /**
538
+ * Creates a custom roles record from an array of role definitions.
539
+ * Use this to pass custom roles to permission checking functions.
540
+ *
541
+ * @param roles - Array of custom role definitions
542
+ * @returns A record indexed by role name
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * const customRoles = buildCustomRolesRecord([blogAuthor, seniorAuthor]);
547
+ * hasPermission("blog-author", { resource: "contentEntries", action: "create" }, customRoles);
548
+ * ```
549
+ */
550
+ export function buildCustomRolesRecord(roles) {
551
+ const result = {};
552
+ for (const role of roles) {
553
+ result[role.name] = role;
554
+ }
555
+ return result;
556
+ }
557
+ /**
558
+ * Checks if a permission applies to a specific content type.
559
+ *
560
+ * @param permission - The permission to check
561
+ * @param contentTypeName - The content type name to check against
562
+ * @returns True if the permission applies to this content type
563
+ */
564
+ function permissionAppliesToContentType(permission, contentTypeName) {
565
+ // If no content type specified, the permission applies
566
+ if (!contentTypeName) {
567
+ return true;
568
+ }
569
+ // If permission has a whitelist, check if content type is in it
570
+ if (permission.contentTypes && permission.contentTypes.length > 0) {
571
+ return permission.contentTypes.includes(contentTypeName);
572
+ }
573
+ // If permission has a blacklist, check if content type is NOT in it
574
+ if (permission.excludeContentTypes &&
575
+ permission.excludeContentTypes.length > 0) {
576
+ return !permission.excludeContentTypes.includes(contentTypeName);
577
+ }
578
+ // No restrictions, permission applies
579
+ return true;
580
+ }
581
+ /**
582
+ * Extended permission check that includes content-type-specific restrictions.
583
+ *
584
+ * Use this function when you need to check if a role can perform an action
585
+ * on a specific content type.
586
+ *
587
+ * @param roleName - The name of the role to check
588
+ * @param permission - The permission to check (resource + action + optional scope)
589
+ * @param options - Additional options including custom roles and content type
590
+ * @returns True if the role has the permission for the specified content type
591
+ *
592
+ * @example
593
+ * ```typescript
594
+ * // Check if blog-author can create blog posts
595
+ * hasContentTypePermission("blog-author", {
596
+ * resource: "contentEntries",
597
+ * action: "create",
598
+ * }, {
599
+ * customRoles: allRoles,
600
+ * contentTypeName: "blog_post",
601
+ * }); // true
602
+ *
603
+ * // Check if blog-author can create legal documents
604
+ * hasContentTypePermission("blog-author", {
605
+ * resource: "contentEntries",
606
+ * action: "create",
607
+ * }, {
608
+ * customRoles: allRoles,
609
+ * contentTypeName: "legal_document",
610
+ * }); // false (restricted to blog_post only)
611
+ * ```
612
+ */
613
+ export function hasContentTypePermission(roleName, permission, options) {
614
+ // Get the role definition
615
+ const role = getRole(roleName, options?.customRoles);
616
+ if (!role) {
617
+ return false;
618
+ }
619
+ // Check if any granted permission matches
620
+ return role.permissions.some((p) => {
621
+ // Check basic permission match (resource, action, scope)
622
+ if (!permissionMatches(p, permission)) {
623
+ return false;
624
+ }
625
+ // Check content type restrictions
626
+ const extendedPerm = p;
627
+ return permissionAppliesToContentType(extendedPerm, options?.contentTypeName);
628
+ });
629
+ }
630
+ /**
631
+ * Gets all content types that a role can perform an action on.
632
+ *
633
+ * @param roleName - The name of the role
634
+ * @param action - The action to check
635
+ * @param options - Additional options
636
+ * @returns Array of content type names, or ["*"] if unrestricted, or [] if no permission
637
+ *
638
+ * @example
639
+ * ```typescript
640
+ * // Get content types the blog-author can create
641
+ * getPermittedContentTypes("blog-author", "create", { customRoles });
642
+ * // Returns: ["blog_post"]
643
+ *
644
+ * // Get content types the editor can update
645
+ * getPermittedContentTypes("editor", "update", { customRoles });
646
+ * // Returns: ["*"] (unrestricted)
647
+ * ```
648
+ */
649
+ export function getPermittedContentTypes(roleName, action, options) {
650
+ const role = getRole(roleName, options?.customRoles);
651
+ if (!role) {
652
+ return [];
653
+ }
654
+ // Find all contentEntries permissions for this action
655
+ const contentPerms = role.permissions.filter((p) => p.resource === "contentEntries" && p.action === action);
656
+ if (contentPerms.length === 0) {
657
+ return [];
658
+ }
659
+ // Check if any permission is unrestricted
660
+ const hasUnrestricted = contentPerms.some((p) => (!p.contentTypes || p.contentTypes.length === 0) &&
661
+ (!p.excludeContentTypes || p.excludeContentTypes.length === 0));
662
+ if (hasUnrestricted) {
663
+ return ["*"]; // Unrestricted access
664
+ }
665
+ // Collect all permitted content types
666
+ const permitted = new Set();
667
+ for (const perm of contentPerms) {
668
+ if (perm.contentTypes) {
669
+ perm.contentTypes.forEach((ct) => permitted.add(ct));
670
+ }
671
+ }
672
+ return Array.from(permitted);
673
+ }
674
+ /**
675
+ * Gets all content types that a role is excluded from for an action.
676
+ *
677
+ * @param roleName - The name of the role
678
+ * @param action - The action to check
679
+ * @param options - Additional options
680
+ * @returns Array of excluded content type names, or [] if none
681
+ */
682
+ export function getExcludedContentTypes(roleName, action, options) {
683
+ const role = getRole(roleName, options?.customRoles);
684
+ if (!role) {
685
+ return [];
686
+ }
687
+ // Find all contentEntries permissions for this action
688
+ const contentPerms = role.permissions.filter((p) => p.resource === "contentEntries" && p.action === action);
689
+ // Collect all excluded content types
690
+ const excluded = new Set();
691
+ for (const perm of contentPerms) {
692
+ if (perm.excludeContentTypes) {
693
+ perm.excludeContentTypes.forEach((ct) => excluded.add(ct));
694
+ }
695
+ }
696
+ return Array.from(excluded);
697
+ }
698
+ // =============================================================================
699
+ // Permission Factory Helpers for Custom Roles
700
+ // =============================================================================
701
+ /**
702
+ * Helper to create a full CRUD permission set for a resource with optional content type restriction.
703
+ *
704
+ * @param resource - The resource to grant permissions on
705
+ * @param options - Optional scope and content type restrictions
706
+ * @returns Array of permissions
707
+ *
708
+ * @example
709
+ * ```typescript
710
+ * // Full CRUD on contentEntries for blog_post only
711
+ * fullCrudForContentType("contentEntries", {
712
+ * contentTypes: ["blog_post"],
713
+ * scope: "own",
714
+ * });
715
+ * ```
716
+ */
717
+ export function fullCrudForContentType(resource, options) {
718
+ const scope = options?.scope ?? "all";
719
+ const base = {
720
+ scope,
721
+ contentTypes: options?.contentTypes,
722
+ excludeContentTypes: options?.excludeContentTypes,
723
+ };
724
+ return [
725
+ { resource, action: "create", ...base },
726
+ { resource, action: "read", ...base },
727
+ { resource, action: "update", ...base },
728
+ { resource, action: "delete", ...base },
729
+ ];
730
+ }
731
+ /**
732
+ * Helper to create publish permissions with optional content type restriction.
733
+ *
734
+ * @param options - Optional scope and content type restrictions
735
+ * @returns Array of publish/unpublish permissions
736
+ */
737
+ export function publishPermissionsForContentType(options) {
738
+ const scope = options?.scope ?? "all";
739
+ const base = {
740
+ scope,
741
+ contentTypes: options?.contentTypes,
742
+ excludeContentTypes: options?.excludeContentTypes,
743
+ };
744
+ return [
745
+ { resource: "contentEntries", action: "publish", ...base },
746
+ { resource: "contentEntries", action: "unpublish", ...base },
747
+ ];
748
+ }
749
+ /**
750
+ * Helper to create read-only permission with optional content type restriction.
751
+ *
752
+ * @param resource - The resource to grant read permission on
753
+ * @param options - Optional scope and content type restrictions
754
+ * @returns Array with single read permission
755
+ */
756
+ export function readOnlyForContentType(resource, options) {
757
+ return [
758
+ {
759
+ resource,
760
+ action: "read",
761
+ scope: options?.scope ?? "all",
762
+ contentTypes: options?.contentTypes,
763
+ excludeContentTypes: options?.excludeContentTypes,
764
+ },
765
+ ];
766
+ }
767
+ // =============================================================================
768
+ // Role Validation Utilities
769
+ // =============================================================================
770
+ /**
771
+ * Validates a custom role configuration.
772
+ *
773
+ * @param config - The custom role configuration to validate
774
+ * @returns An object with isValid boolean and optional error messages
775
+ */
776
+ export function validateCustomRoleConfig(config) {
777
+ const errors = [];
778
+ // Check required fields
779
+ if (!config.name || config.name.trim() === "") {
780
+ errors.push("Role name is required");
781
+ }
782
+ if (!config.displayName || config.displayName.trim() === "") {
783
+ errors.push("Display name is required");
784
+ }
785
+ if (!config.description || config.description.trim() === "") {
786
+ errors.push("Description is required");
787
+ }
788
+ // Check for built-in name conflict
789
+ if (config.name && isBuiltInRole(config.name)) {
790
+ errors.push(`Role name '${config.name}' conflicts with a built-in role`);
791
+ }
792
+ // Validate permissions
793
+ if (!config.permissions || !Array.isArray(config.permissions)) {
794
+ errors.push("Permissions must be an array");
795
+ }
796
+ else {
797
+ for (let i = 0; i < config.permissions.length; i++) {
798
+ const perm = config.permissions[i];
799
+ if (!resources.includes(perm.resource)) {
800
+ errors.push(`Permission ${i}: Invalid resource '${perm.resource}'`);
801
+ }
802
+ if (!actions.includes(perm.action)) {
803
+ errors.push(`Permission ${i}: Invalid action '${perm.action}'`);
804
+ }
805
+ if (perm.scope && perm.scope !== "all" && perm.scope !== "own") {
806
+ errors.push(`Permission ${i}: Invalid scope '${perm.scope}'`);
807
+ }
808
+ // Check for conflicting content type restrictions
809
+ if (perm.contentTypes && perm.excludeContentTypes) {
810
+ if (perm.contentTypes.length > 0 &&
811
+ perm.excludeContentTypes.length > 0) {
812
+ errors.push(`Permission ${i}: Cannot specify both contentTypes and excludeContentTypes`);
813
+ }
814
+ }
815
+ }
816
+ }
817
+ return {
818
+ isValid: errors.length === 0,
819
+ errors,
820
+ };
821
+ }
822
+ /**
823
+ * Validates an extend role configuration.
824
+ *
825
+ * @param config - The extend role configuration to validate
826
+ * @param customRoles - Optional custom roles to check the base role in
827
+ * @returns An object with isValid boolean and optional error messages
828
+ */
829
+ export function validateExtendRoleConfig(config, customRoles) {
830
+ const errors = [];
831
+ // Check required fields
832
+ if (!config.name || config.name.trim() === "") {
833
+ errors.push("Role name is required");
834
+ }
835
+ if (!config.displayName || config.displayName.trim() === "") {
836
+ errors.push("Display name is required");
837
+ }
838
+ if (!config.description || config.description.trim() === "") {
839
+ errors.push("Description is required");
840
+ }
841
+ if (!config.extends || config.extends.trim() === "") {
842
+ errors.push("Base role name (extends) is required");
843
+ }
844
+ // Check for self-reference
845
+ if (config.name === config.extends) {
846
+ errors.push("Cannot extend a role with itself");
847
+ }
848
+ // Check if base role exists
849
+ if (config.extends) {
850
+ const baseRole = getRole(config.extends, customRoles);
851
+ if (!baseRole) {
852
+ errors.push(`Base role '${config.extends}' does not exist`);
853
+ }
854
+ }
855
+ // Validate addPermissions if provided
856
+ if (config.addPermissions) {
857
+ for (let i = 0; i < config.addPermissions.length; i++) {
858
+ const perm = config.addPermissions[i];
859
+ if (!resources.includes(perm.resource)) {
860
+ errors.push(`addPermissions[${i}]: Invalid resource '${perm.resource}'`);
861
+ }
862
+ if (!actions.includes(perm.action)) {
863
+ errors.push(`addPermissions[${i}]: Invalid action '${perm.action}'`);
864
+ }
865
+ }
866
+ }
867
+ // Validate removePermissions if provided
868
+ if (config.removePermissions) {
869
+ for (let i = 0; i < config.removePermissions.length; i++) {
870
+ const perm = config.removePermissions[i];
871
+ if (!resources.includes(perm.resource)) {
872
+ errors.push(`removePermissions[${i}]: Invalid resource '${perm.resource}'`);
873
+ }
874
+ if (!actions.includes(perm.action)) {
875
+ errors.push(`removePermissions[${i}]: Invalid action '${perm.action}'`);
876
+ }
877
+ }
878
+ }
879
+ return {
880
+ isValid: errors.length === 0,
881
+ errors,
882
+ };
883
+ }
884
+ //# sourceMappingURL=roles.js.map