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,768 @@
1
+ /**
2
+ * Content Type Mutation Functions
3
+ *
4
+ * Provides mutation functions for creating, updating, and managing content types.
5
+ * Content types define the schema/blueprint for content entries in the CMS.
6
+ */
7
+ import { v } from "convex/values";
8
+ import { isDeleted } from "./lib/softDelete.js";
9
+ import { mutation } from "./_generated/server.js";
10
+ import { createContentTypeArgs, updateContentTypeArgs, deleteContentTypeArgs, contentTypeDoc, mutationAuthContext, } from "./validators.js";
11
+ import { emitEvent, contentTypeEventType, } from "./eventEmitter.js";
12
+ import { fieldTypes } from "./schema.js";
13
+ import { contentTypeNotFound, contentTypeDeleted, contentTypeNameInvalid, contentTypeNameDuplicate, contentTypeFieldValidationFailed, contentTypeSlugFieldInvalid, contentTypeTitleFieldInvalid, contentTypeHasEntries, contentTypeBreakingChange,
14
+ // batchSizeExceeded,
15
+ internalError, } from "./lib/errors.js";
16
+ import { requireMutationAuth } from "./lib/mutationAuth.js";
17
+ /**
18
+ * Validates the name format for content types and fields.
19
+ * Names must be valid identifiers: lowercase letters, numbers, and underscores.
20
+ * Must start with a letter and be 1-64 characters.
21
+ */
22
+ function isValidName(name) {
23
+ const namePattern = /^[a-z][a-z0-9_]{0,63}$/;
24
+ return namePattern.test(name);
25
+ }
26
+ /**
27
+ * Detects breaking changes between old and new field definitions.
28
+ * Returns an array of breaking changes that would affect existing content entries.
29
+ *
30
+ * @param oldFields - Current field definitions
31
+ * @param newFields - Proposed new field definitions
32
+ * @param existingEntries - Existing content entries to check for impact
33
+ * @returns Array of detected breaking changes with affected entry counts
34
+ */
35
+ function detectBreakingChanges(oldFields, newFields, existingEntries) {
36
+ const breakingChanges = [];
37
+ const oldFieldMap = new Map(oldFields.map((f) => [f.name, f]));
38
+ const newFieldMap = new Map(newFields.map((f) => [f.name, f]));
39
+ // Check for removed fields that have data in existing entries
40
+ for (const oldField of oldFields) {
41
+ if (!newFieldMap.has(oldField.name)) {
42
+ // Field is being removed - count entries with data in this field
43
+ const affectedCount = existingEntries.filter((entry) => {
44
+ const value = entry.data[oldField.name];
45
+ return value !== undefined && value !== null && value !== "";
46
+ }).length;
47
+ if (affectedCount > 0) {
48
+ breakingChanges.push({
49
+ type: "FIELD_REMOVED",
50
+ fieldName: oldField.name,
51
+ message: `Removing field "${oldField.name}" will delete data from ${affectedCount} existing entries`,
52
+ affectedEntriesCount: affectedCount,
53
+ });
54
+ }
55
+ }
56
+ }
57
+ // Check for changes to existing fields
58
+ for (const newField of newFields) {
59
+ const oldField = oldFieldMap.get(newField.name);
60
+ if (!oldField)
61
+ continue; // New field, no breaking change possible
62
+ // Check for type changes
63
+ if (oldField.type !== newField.type) {
64
+ const affectedCount = existingEntries.filter((entry) => {
65
+ const value = entry.data[newField.name];
66
+ return value !== undefined && value !== null;
67
+ }).length;
68
+ if (affectedCount > 0) {
69
+ breakingChanges.push({
70
+ type: "FIELD_TYPE_CHANGED",
71
+ fieldName: newField.name,
72
+ message: `Changing field "${newField.name}" type from "${oldField.type}" to "${newField.type}" may invalidate ${affectedCount} existing entries`,
73
+ affectedEntriesCount: affectedCount,
74
+ });
75
+ }
76
+ }
77
+ // Check for optional -> required changes
78
+ if (!oldField.required && newField.required) {
79
+ const affectedCount = existingEntries.filter((entry) => {
80
+ const value = entry.data[newField.name];
81
+ return value === undefined || value === null || value === "";
82
+ }).length;
83
+ if (affectedCount > 0) {
84
+ breakingChanges.push({
85
+ type: "FIELD_MADE_REQUIRED",
86
+ fieldName: newField.name,
87
+ message: `Making field "${newField.name}" required will invalidate ${affectedCount} entries with missing values`,
88
+ affectedEntriesCount: affectedCount,
89
+ });
90
+ }
91
+ }
92
+ // Check for removed select/multiSelect options
93
+ if ((oldField.type === "select" || oldField.type === "multiSelect") &&
94
+ oldField.options?.options &&
95
+ newField.options?.options) {
96
+ const oldOptions = new Set(oldField.options.options.map((o) => o.value));
97
+ const newOptions = new Set(newField.options.options.map((o) => o.value));
98
+ const removedOptions = [...oldOptions].filter((o) => !newOptions.has(o));
99
+ if (removedOptions.length > 0) {
100
+ const affectedCount = existingEntries.filter((entry) => {
101
+ const value = entry.data[newField.name];
102
+ if (oldField.type === "select") {
103
+ return removedOptions.includes(value);
104
+ }
105
+ else {
106
+ // multiSelect - check if any values are in removed options
107
+ const values = value;
108
+ return values?.some((v) => removedOptions.includes(v));
109
+ }
110
+ }).length;
111
+ if (affectedCount > 0) {
112
+ breakingChanges.push({
113
+ type: "SELECT_OPTIONS_REMOVED",
114
+ fieldName: newField.name,
115
+ message: `Removing options [${removedOptions.join(", ")}] from "${newField.name}" will invalidate ${affectedCount} entries using those values`,
116
+ affectedEntriesCount: affectedCount,
117
+ });
118
+ }
119
+ }
120
+ }
121
+ // Check for restricted reference content types
122
+ if (oldField.type === "reference" &&
123
+ newField.type === "reference" &&
124
+ oldField.options?.allowedContentTypes &&
125
+ newField.options?.allowedContentTypes) {
126
+ const oldAllowed = new Set(oldField.options.allowedContentTypes);
127
+ const newAllowed = new Set(newField.options.allowedContentTypes);
128
+ const removedTypes = [...oldAllowed].filter((t) => !newAllowed.has(t));
129
+ // Note: We can't easily check if existing references point to removed types
130
+ // without resolving references. This is a warning-level change.
131
+ if (removedTypes.length > 0) {
132
+ breakingChanges.push({
133
+ type: "REFERENCE_TYPES_RESTRICTED",
134
+ fieldName: newField.name,
135
+ message: `Restricting allowed content types for "${newField.name}" by removing [${removedTypes.join(", ")}] may invalidate existing references`,
136
+ affectedEntriesCount: existingEntries.length, // Potentially all entries
137
+ });
138
+ }
139
+ }
140
+ // Check for tightened validation (minLength increased, maxLength decreased, etc.)
141
+ if (oldField.type === "text" &&
142
+ newField.type === "text" &&
143
+ oldField.options &&
144
+ newField.options) {
145
+ const violations = [];
146
+ // Check if minLength was increased
147
+ if (newField.options.minLength !== undefined &&
148
+ (oldField.options.minLength === undefined ||
149
+ newField.options.minLength > oldField.options.minLength)) {
150
+ violations.push(`minLength increased to ${newField.options.minLength}`);
151
+ }
152
+ // Check if maxLength was decreased
153
+ if (newField.options.maxLength !== undefined &&
154
+ oldField.options.maxLength !== undefined &&
155
+ newField.options.maxLength < oldField.options.maxLength) {
156
+ violations.push(`maxLength decreased to ${newField.options.maxLength}`);
157
+ }
158
+ if (violations.length > 0) {
159
+ const affectedCount = existingEntries.filter((entry) => {
160
+ const value = entry.data[newField.name];
161
+ if (typeof value !== "string")
162
+ return false;
163
+ if (newField.options?.minLength !== undefined &&
164
+ value.length < newField.options.minLength) {
165
+ return true;
166
+ }
167
+ if (newField.options?.maxLength !== undefined &&
168
+ value.length > newField.options.maxLength) {
169
+ return true;
170
+ }
171
+ return false;
172
+ }).length;
173
+ if (affectedCount > 0) {
174
+ breakingChanges.push({
175
+ type: "VALIDATION_TIGHTENED",
176
+ fieldName: newField.name,
177
+ message: `Tightening validation for "${newField.name}" (${violations.join(", ")}) will invalidate ${affectedCount} entries`,
178
+ affectedEntriesCount: affectedCount,
179
+ });
180
+ }
181
+ }
182
+ }
183
+ }
184
+ return breakingChanges;
185
+ }
186
+ /**
187
+ * Validates field definitions for a content type.
188
+ * Checks for:
189
+ * - Unique field names
190
+ * - Valid field types
191
+ * - Required properties (name, label, type, required)
192
+ * - Valid field name format
193
+ * - Select/multiSelect fields have options defined
194
+ */
195
+ function validateFieldDefinitions(fields) {
196
+ const errors = [];
197
+ const seenNames = new Set();
198
+ for (const field of fields) {
199
+ // Check for missing required properties
200
+ if (!field.name || typeof field.name !== "string") {
201
+ errors.push({
202
+ fieldName: field.name || "(unnamed)",
203
+ message: "Field must have a name property",
204
+ code: "MISSING_REQUIRED_PROPERTY",
205
+ });
206
+ continue;
207
+ }
208
+ if (!field.label || typeof field.label !== "string") {
209
+ errors.push({
210
+ fieldName: field.name,
211
+ message: `Field "${field.name}" must have a label property`,
212
+ code: "MISSING_REQUIRED_PROPERTY",
213
+ });
214
+ }
215
+ if (!field.type || typeof field.type !== "string") {
216
+ errors.push({
217
+ fieldName: field.name,
218
+ message: `Field "${field.name}" must have a type property`,
219
+ code: "MISSING_REQUIRED_PROPERTY",
220
+ });
221
+ }
222
+ if (typeof field.required !== "boolean") {
223
+ errors.push({
224
+ fieldName: field.name,
225
+ message: `Field "${field.name}" must have a required property (boolean)`,
226
+ code: "MISSING_REQUIRED_PROPERTY",
227
+ });
228
+ }
229
+ // Validate field name format
230
+ if (field.name && !isValidName(field.name)) {
231
+ errors.push({
232
+ fieldName: field.name,
233
+ message: `Field name "${field.name}" must start with a lowercase letter and contain only lowercase letters, numbers, and underscores (max 64 chars)`,
234
+ code: "INVALID_FIELD_NAME",
235
+ });
236
+ }
237
+ // Check for duplicate field names
238
+ if (seenNames.has(field.name)) {
239
+ errors.push({
240
+ fieldName: field.name,
241
+ message: `Duplicate field name: "${field.name}"`,
242
+ code: "DUPLICATE_FIELD_NAME",
243
+ });
244
+ }
245
+ seenNames.add(field.name);
246
+ // Validate field type is one of the supported types
247
+ if (field.type && !fieldTypes.includes(field.type)) {
248
+ errors.push({
249
+ fieldName: field.name,
250
+ message: `Invalid field type "${field.type}". Must be one of: ${fieldTypes.join(", ")}`,
251
+ code: "INVALID_FIELD_TYPE",
252
+ });
253
+ }
254
+ // Validate select/multiSelect fields have options
255
+ if ((field.type === "select" || field.type === "multiSelect") &&
256
+ (!field.options?.options || field.options.options.length === 0)) {
257
+ errors.push({
258
+ fieldName: field.name,
259
+ message: `${field.type} field "${field.name}" must have options defined`,
260
+ code: "INVALID_SELECT_OPTIONS",
261
+ });
262
+ }
263
+ }
264
+ return errors;
265
+ }
266
+ /**
267
+ * Mutation to create a new content type.
268
+ *
269
+ * Creates a content type definition with a unique name, display name, and
270
+ * field definitions. The content type can then be used to create content entries.
271
+ *
272
+ * @param name - Unique machine-readable name (e.g., "blog_post")
273
+ * @param displayName - Human-readable name (e.g., "Blog Post")
274
+ * @param description - Optional description of the content type
275
+ * @param fields - Array of field definitions
276
+ * @param icon - Optional icon identifier for UI
277
+ * @param singleton - If true, only one entry of this type can exist
278
+ * @param slugField - Field name to use for slug generation (defaults to first text field)
279
+ * @param titleField - Field name to use as display title (defaults to first text field)
280
+ * @param sortOrder - Custom sort order for admin UI
281
+ * @param createdBy - User ID who is creating this content type
282
+ *
283
+ * @returns The created content type document
284
+ *
285
+ * @throws Error if the name is not unique
286
+ * @throws Error if the name format is invalid
287
+ * @throws Error if field definitions are invalid
288
+ *
289
+ * @example
290
+ * ```typescript
291
+ * const blogPost = await ctx.runMutation(api.contentTypeMutations.createContentType, {
292
+ * name: "blog_post",
293
+ * displayName: "Blog Post",
294
+ * description: "Articles for the company blog",
295
+ * fields: [
296
+ * { name: "title", label: "Title", type: "text", required: true },
297
+ * { name: "content", label: "Content", type: "richText", required: true },
298
+ * { name: "published_date", label: "Published Date", type: "date", required: false },
299
+ * ],
300
+ * slugField: "title",
301
+ * titleField: "title",
302
+ * createdBy: currentUserId,
303
+ * });
304
+ * ```
305
+ */
306
+ export const createContentType = mutation({
307
+ args: {
308
+ ...createContentTypeArgs.fields,
309
+ /** Optional auth context for mutation-level authorization */
310
+ _auth: v.optional(mutationAuthContext),
311
+ },
312
+ returns: contentTypeDoc,
313
+ handler: async (ctx, args) => {
314
+ const { name, displayName, description, fields, icon, singleton, slugField, titleField, sortOrder, createdBy, _auth, } = args;
315
+ // Authorization check - contentTypes.create permission
316
+ requireMutationAuth(_auth, "contentTypes", "create");
317
+ // Validate content type name format
318
+ if (!isValidName(name)) {
319
+ throw contentTypeNameInvalid(name);
320
+ }
321
+ // Check if name is already taken (must be unique)
322
+ const existingType = await ctx.db
323
+ .query("contentTypes")
324
+ .withIndex("by_name", (q) => q.eq("name", name))
325
+ .first();
326
+ if (existingType) {
327
+ throw contentTypeNameDuplicate(name);
328
+ }
329
+ // Validate field definitions
330
+ const fieldErrors = validateFieldDefinitions(fields);
331
+ if (fieldErrors.length > 0) {
332
+ throw contentTypeFieldValidationFailed(fieldErrors);
333
+ }
334
+ // Validate slugField references an existing field if provided
335
+ const fieldNames = fields.map((f) => f.name);
336
+ if (slugField) {
337
+ const slugFieldExists = fields.some((f) => f.name === slugField);
338
+ if (!slugFieldExists) {
339
+ throw contentTypeSlugFieldInvalid(slugField, fieldNames);
340
+ }
341
+ }
342
+ // Validate titleField references an existing field if provided
343
+ if (titleField) {
344
+ const titleFieldExists = fields.some((f) => f.name === titleField);
345
+ if (!titleFieldExists) {
346
+ throw contentTypeTitleFieldInvalid(titleField, fieldNames);
347
+ }
348
+ }
349
+ // Insert the new content type
350
+ const id = await ctx.db.insert("contentTypes", {
351
+ name,
352
+ displayName,
353
+ description,
354
+ fields,
355
+ icon,
356
+ singleton,
357
+ slugField,
358
+ titleField,
359
+ sortOrder,
360
+ isActive: true,
361
+ createdBy,
362
+ });
363
+ // Retrieve and return the created document
364
+ const created = await ctx.db.get(id);
365
+ if (!created) {
366
+ throw internalError("Failed to retrieve created content type");
367
+ }
368
+ // Emit content type created event
369
+ await emitEvent(ctx, {
370
+ eventType: contentTypeEventType("created"),
371
+ resourceType: "contentType",
372
+ resourceId: id,
373
+ action: "created",
374
+ payload: {
375
+ name,
376
+ displayName,
377
+ fieldCount: fields.length,
378
+ isActive: true,
379
+ },
380
+ userId: createdBy,
381
+ });
382
+ return created;
383
+ },
384
+ });
385
+ // =============================================================================
386
+ // Update Content Type Mutation
387
+ // =============================================================================
388
+ /**
389
+ * Validator for breaking change information returned by the mutation.
390
+ */
391
+ const breakingChangeValidator = v.object({
392
+ type: v.union(v.literal("FIELD_REMOVED"), v.literal("FIELD_TYPE_CHANGED"), v.literal("FIELD_MADE_REQUIRED"), v.literal("SELECT_OPTIONS_REMOVED"), v.literal("REFERENCE_TYPES_RESTRICTED"), v.literal("VALIDATION_TIGHTENED")),
393
+ fieldName: v.string(),
394
+ message: v.string(),
395
+ affectedEntriesCount: v.number(),
396
+ });
397
+ /**
398
+ * Extended return type that includes breaking change warnings.
399
+ */
400
+ const updateContentTypeResult = v.object({
401
+ ...contentTypeDoc.fields,
402
+ /** Breaking changes that were detected (only populated if force=true was used) */
403
+ breakingChanges: v.optional(v.array(breakingChangeValidator)),
404
+ });
405
+ /**
406
+ * Mutation to update an existing content type's fields and configuration.
407
+ *
408
+ * Includes validation to prevent breaking changes to fields with existing content.
409
+ * When breaking changes are detected and `force` is not set to true, the mutation
410
+ * will throw an error with details about the breaking changes.
411
+ *
412
+ * **Breaking Change Detection:**
413
+ * - Removing fields that have data in existing entries
414
+ * - Changing field types (e.g., text → number)
415
+ * - Making optional fields required when entries have empty values
416
+ * - Removing select/multiSelect options that are in use
417
+ * - Restricting allowed reference content types
418
+ * - Tightening validation rules (increased minLength, decreased maxLength)
419
+ *
420
+ * @param id - The content type ID to update
421
+ * @param displayName - Optional new display name
422
+ * @param description - Optional new description
423
+ * @param fields - Optional new field definitions (replaces all existing fields)
424
+ * @param icon - Optional new icon
425
+ * @param singleton - Optional singleton flag
426
+ * @param slugField - Optional field name for slug generation
427
+ * @param titleField - Optional field name for display title
428
+ * @param sortOrder - Optional new sort order
429
+ * @param isActive - Optional active status
430
+ * @param updatedBy - User ID making the update (for audit trail)
431
+ * @param force - If true, allow breaking changes (default: false)
432
+ *
433
+ * @returns The updated content type, with breakingChanges if force was used
434
+ *
435
+ * @throws Error if the content type does not exist
436
+ * @throws Error if breaking changes are detected and force is not true
437
+ * @throws Error if field definitions are invalid
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * // Simple update (no breaking changes)
442
+ * const updated = await ctx.runMutation(api.contentTypeMutations.updateContentType, {
443
+ * id: contentTypeId,
444
+ * displayName: "Updated Blog Post",
445
+ * description: "New description",
446
+ * updatedBy: currentUserId,
447
+ * });
448
+ *
449
+ * // Update fields (will check for breaking changes)
450
+ * const updated = await ctx.runMutation(api.contentTypeMutations.updateContentType, {
451
+ * id: contentTypeId,
452
+ * fields: [
453
+ * { name: "title", label: "Title", type: "text", required: true },
454
+ * { name: "content", label: "Content", type: "richText", required: true },
455
+ * { name: "author", label: "Author", type: "text", required: false }, // New field
456
+ * ],
457
+ * updatedBy: currentUserId,
458
+ * });
459
+ *
460
+ * // Force update with breaking changes
461
+ * const updated = await ctx.runMutation(api.contentTypeMutations.updateContentType, {
462
+ * id: contentTypeId,
463
+ * fields: newFields,
464
+ * force: true, // Acknowledge potential data loss
465
+ * updatedBy: currentUserId,
466
+ * });
467
+ * ```
468
+ */
469
+ export const updateContentType = mutation({
470
+ args: {
471
+ ...updateContentTypeArgs.fields,
472
+ /** If true, allow breaking changes that may affect existing content entries */
473
+ force: v.optional(v.boolean()),
474
+ /** Optional auth context for mutation-level authorization */
475
+ _auth: v.optional(mutationAuthContext),
476
+ },
477
+ returns: updateContentTypeResult,
478
+ handler: async (ctx, args) => {
479
+ const { id, displayName, description, fields, icon, singleton, slugField, titleField, sortOrder, isActive, updatedBy, force = false, _auth, } = args;
480
+ // Authorization check - contentTypes.update permission
481
+ requireMutationAuth(_auth, "contentTypes", "update");
482
+ const existingType = await ctx.db.get(id);
483
+ if (!existingType) {
484
+ throw contentTypeNotFound(id);
485
+ }
486
+ if (isDeleted(existingType)) {
487
+ throw contentTypeDeleted(id, existingType.name);
488
+ }
489
+ // Build the update object with only provided fields
490
+ const updates = {
491
+ updatedBy,
492
+ };
493
+ // Handle simple field updates
494
+ if (displayName !== undefined) {
495
+ updates.displayName = displayName;
496
+ }
497
+ if (description !== undefined) {
498
+ updates.description = description;
499
+ }
500
+ if (icon !== undefined) {
501
+ updates.icon = icon;
502
+ }
503
+ if (singleton !== undefined) {
504
+ updates.singleton = singleton;
505
+ }
506
+ if (sortOrder !== undefined) {
507
+ updates.sortOrder = sortOrder;
508
+ }
509
+ if (isActive !== undefined) {
510
+ updates.isActive = isActive;
511
+ }
512
+ // Track breaking changes if fields are being updated
513
+ let detectedBreakingChanges = [];
514
+ // Handle field updates with breaking change detection
515
+ if (fields !== undefined) {
516
+ // Validate the new field definitions
517
+ const fieldErrors = validateFieldDefinitions(fields);
518
+ if (fieldErrors.length > 0) {
519
+ throw contentTypeFieldValidationFailed(fieldErrors);
520
+ }
521
+ // Get all existing content entries for this content type
522
+ const existingEntries = await ctx.db
523
+ .query("contentEntries")
524
+ .withIndex("by_content_type", (q) => q.eq("contentTypeId", id))
525
+ .filter((q) => q.eq(q.field("deletedAt"), undefined))
526
+ .collect();
527
+ // Only check for breaking changes if there are existing entries
528
+ if (existingEntries.length > 0) {
529
+ detectedBreakingChanges = detectBreakingChanges(existingType.fields, fields, existingEntries.map((e) => ({ data: e.data })));
530
+ // If breaking changes detected and force is not true, throw error
531
+ if (detectedBreakingChanges.length > 0 && !force) {
532
+ throw contentTypeBreakingChange(detectedBreakingChanges);
533
+ }
534
+ }
535
+ updates.fields = fields;
536
+ }
537
+ // Validate slugField references an existing field if provided
538
+ const effectiveFields = (fields ?? existingType.fields);
539
+ const effectiveSlugField = slugField !== undefined ? slugField : existingType.slugField;
540
+ const effectiveTitleField = titleField !== undefined ? titleField : existingType.titleField;
541
+ const availableFieldNames = effectiveFields.map((f) => f.name);
542
+ if (effectiveSlugField) {
543
+ const slugFieldExists = effectiveFields.some((f) => f.name === effectiveSlugField);
544
+ if (!slugFieldExists) {
545
+ throw contentTypeSlugFieldInvalid(effectiveSlugField, availableFieldNames);
546
+ }
547
+ if (slugField !== undefined) {
548
+ updates.slugField = slugField;
549
+ }
550
+ }
551
+ else if (slugField !== undefined) {
552
+ updates.slugField = slugField;
553
+ }
554
+ if (effectiveTitleField) {
555
+ const titleFieldExists = effectiveFields.some((f) => f.name === effectiveTitleField);
556
+ if (!titleFieldExists) {
557
+ throw contentTypeTitleFieldInvalid(effectiveTitleField, availableFieldNames);
558
+ }
559
+ if (titleField !== undefined) {
560
+ updates.titleField = titleField;
561
+ }
562
+ }
563
+ else if (titleField !== undefined) {
564
+ updates.titleField = titleField;
565
+ }
566
+ // Apply the updates
567
+ await ctx.db.patch(id, updates);
568
+ // Retrieve and return the updated document
569
+ const updated = await ctx.db.get(id);
570
+ if (!updated) {
571
+ throw internalError("Failed to retrieve updated content type");
572
+ }
573
+ // Emit content type updated event
574
+ const changedFields = Object.keys(updates).filter((k) => k !== "updatedBy");
575
+ await emitEvent(ctx, {
576
+ eventType: contentTypeEventType("updated"),
577
+ resourceType: "contentType",
578
+ resourceId: id,
579
+ action: "updated",
580
+ payload: {
581
+ name: updated.name,
582
+ displayName: updated.displayName,
583
+ fieldCount: updated.fields.length,
584
+ isActive: updated.isActive,
585
+ changedFields,
586
+ },
587
+ userId: updatedBy,
588
+ });
589
+ // Include breaking changes in the result if force was used
590
+ return {
591
+ ...updated,
592
+ breakingChanges: detectedBreakingChanges.length > 0 ? detectedBreakingChanges : undefined,
593
+ };
594
+ },
595
+ });
596
+ // =============================================================================
597
+ // Delete Content Type Mutation
598
+ // =============================================================================
599
+ /**
600
+ * Result type for the delete content type mutation.
601
+ * Includes information about any cascade-deleted entries.
602
+ */
603
+ const deleteContentTypeResult = v.object({
604
+ /** Whether the deletion was successful */
605
+ success: v.boolean(),
606
+ /** The ID of the deleted content type */
607
+ deletedId: v.id("contentTypes"),
608
+ /** Number of content entries that were deleted (when cascade=true) */
609
+ deletedEntriesCount: v.number(),
610
+ /** Number of content versions that were deleted (when cascade=true and hardDelete=true) */
611
+ deletedVersionsCount: v.number(),
612
+ /** Whether this was a hard delete (permanent) or soft delete */
613
+ wasHardDelete: v.boolean(),
614
+ });
615
+ /**
616
+ * Mutation to delete a content type.
617
+ *
618
+ * Supports two deletion strategies via the `cascade` flag:
619
+ * 1. **Cascade delete** (`cascade: true`): Deletes all content entries of this type
620
+ * before deleting the content type itself.
621
+ * 2. **Prevent if entries exist** (`cascade: false` or not specified): Fails the
622
+ * deletion if any content entries exist for this type.
623
+ *
624
+ * Also supports two deletion modes via the `hardDelete` flag:
625
+ * - **Soft delete** (default): Sets `deletedAt` timestamp, entries remain in database
626
+ * - **Hard delete** (`hardDelete: true`): Permanently removes from database
627
+ *
628
+ * @param id - The content type ID to delete
629
+ * @param cascade - If true, delete all entries of this type first. Default: false
630
+ * @param hardDelete - If true, permanently delete. Default: false (soft delete)
631
+ * @param deletedBy - User ID performing the deletion (for audit trail)
632
+ *
633
+ * @returns Object with deletion results including counts of deleted entries/versions
634
+ *
635
+ * @throws Error if content type does not exist
636
+ * @throws Error if content type is already deleted (soft deleted)
637
+ * @throws Error if cascade is false and content entries exist
638
+ *
639
+ * @example
640
+ * ```typescript
641
+ * // Soft delete - fails if entries exist
642
+ * const result = await ctx.runMutation(api.contentTypeMutations.deleteContentType, {
643
+ * id: contentTypeId,
644
+ * deletedBy: currentUserId,
645
+ * });
646
+ *
647
+ * // Cascade soft delete - deletes all entries too
648
+ * const result = await ctx.runMutation(api.contentTypeMutations.deleteContentType, {
649
+ * id: contentTypeId,
650
+ * cascade: true,
651
+ * deletedBy: currentUserId,
652
+ * });
653
+ *
654
+ * // Hard delete with cascade - permanently removes everything
655
+ * const result = await ctx.runMutation(api.contentTypeMutations.deleteContentType, {
656
+ * id: contentTypeId,
657
+ * cascade: true,
658
+ * hardDelete: true,
659
+ * deletedBy: currentUserId,
660
+ * });
661
+ * ```
662
+ */
663
+ export const deleteContentType = mutation({
664
+ args: {
665
+ ...deleteContentTypeArgs.fields,
666
+ /** Optional auth context for mutation-level authorization */
667
+ _auth: v.optional(mutationAuthContext),
668
+ },
669
+ returns: deleteContentTypeResult,
670
+ handler: async (ctx, args) => {
671
+ const { id, cascade = false, hardDelete = false, deletedBy, _auth } = args;
672
+ // Authorization check - contentTypes.delete permission
673
+ requireMutationAuth(_auth, "contentTypes", "delete");
674
+ const contentType = await ctx.db.get(id);
675
+ if (!contentType) {
676
+ throw contentTypeNotFound(id);
677
+ }
678
+ // Check if already soft-deleted
679
+ if (isDeleted(contentType)) {
680
+ throw contentTypeDeleted(id, contentType.name);
681
+ }
682
+ // Get all content entries for this type (excluding already soft-deleted ones)
683
+ const existingEntries = await ctx.db
684
+ .query("contentEntries")
685
+ .withIndex("by_content_type", (q) => q.eq("contentTypeId", id))
686
+ .filter((q) => q.eq(q.field("deletedAt"), undefined))
687
+ .collect();
688
+ const entryCount = existingEntries.length;
689
+ // If entries exist and cascade is false, prevent deletion
690
+ if (entryCount > 0 && !cascade) {
691
+ throw contentTypeHasEntries(id, contentType.name, entryCount);
692
+ }
693
+ let deletedEntriesCount = 0;
694
+ let deletedVersionsCount = 0;
695
+ const now = Date.now();
696
+ // If cascade is true, delete all entries first
697
+ if (cascade && entryCount > 0) {
698
+ if (hardDelete) {
699
+ // Hard delete: permanently remove entries and their versions
700
+ for (const entry of existingEntries) {
701
+ // Delete all versions for this entry
702
+ const versions = await ctx.db
703
+ .query("contentVersions")
704
+ .withIndex("by_entry", (q) => q.eq("entryId", entry._id))
705
+ .collect();
706
+ for (const version of versions) {
707
+ await ctx.db.delete(version._id);
708
+ deletedVersionsCount++;
709
+ }
710
+ // Delete the entry
711
+ await ctx.db.delete(entry._id);
712
+ deletedEntriesCount++;
713
+ }
714
+ }
715
+ else {
716
+ // Soft delete: set deletedAt on all entries
717
+ for (const entry of existingEntries) {
718
+ await ctx.db.patch(entry._id, {
719
+ deletedAt: now,
720
+ updatedBy: deletedBy,
721
+ });
722
+ deletedEntriesCount++;
723
+ }
724
+ }
725
+ }
726
+ // Delete the content type itself
727
+ if (hardDelete) {
728
+ // Hard delete: permanently remove
729
+ await ctx.db.delete(id);
730
+ }
731
+ else {
732
+ // Soft delete: set deletedAt
733
+ await ctx.db.patch(id, {
734
+ deletedAt: now,
735
+ isActive: false,
736
+ updatedBy: deletedBy,
737
+ });
738
+ }
739
+ // Emit content type deleted event
740
+ await emitEvent(ctx, {
741
+ eventType: contentTypeEventType("deleted"),
742
+ resourceType: "contentType",
743
+ resourceId: id,
744
+ action: "deleted",
745
+ payload: {
746
+ name: contentType.name,
747
+ displayName: contentType.displayName,
748
+ fieldCount: contentType.fields.length,
749
+ isActive: false,
750
+ },
751
+ userId: deletedBy,
752
+ metadata: {
753
+ hardDelete,
754
+ cascade,
755
+ deletedEntriesCount,
756
+ deletedVersionsCount,
757
+ },
758
+ });
759
+ return {
760
+ success: true,
761
+ deletedId: id,
762
+ deletedEntriesCount,
763
+ deletedVersionsCount,
764
+ wasHardDelete: hardDelete,
765
+ };
766
+ },
767
+ });
768
+ //# sourceMappingURL=contentTypeMutations.js.map