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,430 @@
1
+ /**
2
+ * Reference Resolution Utilities
3
+ *
4
+ * Provides functions for resolving and populating content references.
5
+ * These utilities help with fetching referenced content entries and
6
+ * validating reference constraints.
7
+ */
8
+
9
+ import { Id } from "../_generated/dataModel.js";
10
+ import { isDeleted } from "./softDelete.js";
11
+ import { QueryCtx } from "../_generated/server.js";
12
+
13
+ // =============================================================================
14
+ // Types
15
+ // =============================================================================
16
+
17
+ /**
18
+ * A single reference value (content entry ID as string)
19
+ */
20
+ export type SingleReference = string;
21
+
22
+ /**
23
+ * Multiple reference values (array of content entry IDs)
24
+ */
25
+ export type MultipleReferences = string[];
26
+
27
+ /**
28
+ * Reference field value - either single or multiple based on field configuration
29
+ */
30
+ export type ReferenceValue = SingleReference | MultipleReferences;
31
+
32
+ /**
33
+ * A resolved reference with the full content entry data
34
+ */
35
+ export interface ResolvedReference {
36
+ /** The content entry ID */
37
+ id: string;
38
+ /** The content type name */
39
+ contentTypeName: string;
40
+ /** The content type display name */
41
+ contentTypeDisplayName: string;
42
+ /** The entry's slug */
43
+ slug: string;
44
+ /** The entry's status (supports custom workflow states) */
45
+ status: string;
46
+ /** The entry's data (field values) */
47
+ data: Record<string, unknown>;
48
+ /** Whether the entry exists and is not deleted */
49
+ exists: boolean;
50
+ }
51
+
52
+ /**
53
+ * Options for resolving references
54
+ */
55
+ export interface ResolveOptions {
56
+ /** Include soft-deleted entries (default: false) */
57
+ includeDeleted?: boolean;
58
+ /** Only return published entries (default: false) */
59
+ publishedOnly?: boolean;
60
+ /** Specific fields to include from the entry data (default: all) */
61
+ fields?: string[];
62
+ }
63
+
64
+ /**
65
+ * Result of a reference resolution operation
66
+ */
67
+ export interface ResolveResult {
68
+ /** Successfully resolved references */
69
+ resolved: ResolvedReference[];
70
+ /** IDs that could not be resolved (not found or deleted) */
71
+ unresolved: string[];
72
+ }
73
+
74
+ // =============================================================================
75
+ // Core Resolution Functions
76
+ // =============================================================================
77
+
78
+ /**
79
+ * Resolve a single reference to its full content entry.
80
+ *
81
+ * @param ctx - Convex query context
82
+ * @param referenceId - The content entry ID to resolve
83
+ * @param options - Resolution options
84
+ * @returns The resolved reference or null if not found
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * // In a query handler:
89
+ * const author = await resolveReference(ctx, entry.data.authorId);
90
+ * if (author) {
91
+ * console.log("Author:", author.data.name);
92
+ * }
93
+ * ```
94
+ */
95
+ export async function resolveReference(
96
+ ctx: QueryCtx,
97
+ referenceId: string,
98
+ options: ResolveOptions = {},
99
+ ): Promise<ResolvedReference | null> {
100
+ const { includeDeleted = false, publishedOnly = false } = options;
101
+
102
+ try {
103
+ // Get the content entry
104
+ const entry = await ctx.db.get(referenceId as Id<"contentEntries">);
105
+
106
+ if (!entry) {
107
+ return null;
108
+ }
109
+
110
+ // Check soft-delete status
111
+ if (!includeDeleted && isDeleted(entry)) {
112
+ return null;
113
+ }
114
+
115
+ // Check published status
116
+ if (publishedOnly && entry.status !== "published") {
117
+ return null;
118
+ }
119
+
120
+ // Get the content type for this entry
121
+ const contentType = await ctx.db.get(entry.contentTypeId);
122
+
123
+ if (!contentType || isDeleted(contentType)) {
124
+ return null;
125
+ }
126
+
127
+ // Filter fields if specified
128
+ let data = entry.data as Record<string, unknown>;
129
+ if (options.fields && options.fields.length > 0) {
130
+ const filteredData: Record<string, unknown> = {};
131
+ for (const field of options.fields) {
132
+ if (field in data) {
133
+ filteredData[field] = data[field];
134
+ }
135
+ }
136
+ data = filteredData;
137
+ }
138
+
139
+ return {
140
+ id: referenceId,
141
+ contentTypeName: contentType.name,
142
+ contentTypeDisplayName: contentType.displayName,
143
+ slug: entry.slug,
144
+ status: entry.status,
145
+ data,
146
+ exists: true,
147
+ };
148
+ } catch {
149
+ // Invalid ID format or other error
150
+ return null;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Resolve multiple references to their full content entries.
156
+ *
157
+ * @param ctx - Convex query context
158
+ * @param referenceIds - Array of content entry IDs to resolve
159
+ * @param options - Resolution options
160
+ * @returns Result with resolved references and unresolved IDs
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * // In a query handler:
165
+ * const result = await resolveReferences(ctx, entry.data.relatedPostIds, {
166
+ * publishedOnly: true,
167
+ * });
168
+ *
169
+ * console.log("Found:", result.resolved.length);
170
+ * console.log("Missing:", result.unresolved);
171
+ * ```
172
+ */
173
+ export async function resolveReferences(
174
+ ctx: QueryCtx,
175
+ referenceIds: string[],
176
+ options: ResolveOptions = {},
177
+ ): Promise<ResolveResult> {
178
+ const resolved: ResolvedReference[] = [];
179
+ const unresolved: string[] = [];
180
+
181
+ // Resolve each reference in parallel for efficiency
182
+ const promises = referenceIds.map(async (id) => {
183
+ const result = await resolveReference(ctx, id, options);
184
+ return { id, result };
185
+ });
186
+
187
+ const results = await Promise.all(promises);
188
+
189
+ for (const { id, result } of results) {
190
+ if (result) {
191
+ resolved.push(result);
192
+ } else {
193
+ unresolved.push(id);
194
+ }
195
+ }
196
+
197
+ return { resolved, unresolved };
198
+ }
199
+
200
+ /**
201
+ * Check if a reference ID points to a valid, existing content entry.
202
+ *
203
+ * @param ctx - Convex query context
204
+ * @param referenceId - The content entry ID to check
205
+ * @param allowedContentTypes - Optional array of allowed content type names
206
+ * @returns Object with validity status and optional error message
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * // Validate a reference before saving:
211
+ * const check = await isValidReference(ctx, authorId, ["user"]);
212
+ * if (!check.valid) {
213
+ * throw new Error(check.error);
214
+ * }
215
+ * ```
216
+ */
217
+ export async function isValidReference(
218
+ ctx: QueryCtx,
219
+ referenceId: string,
220
+ allowedContentTypes?: string[],
221
+ ): Promise<{ valid: boolean; error?: string }> {
222
+ try {
223
+ // Get the content entry
224
+ const entry = await ctx.db.get(referenceId as Id<"contentEntries">);
225
+
226
+ if (!entry) {
227
+ return { valid: false, error: `Content entry not found: ${referenceId}` };
228
+ }
229
+
230
+ // Check soft-delete status
231
+ if (isDeleted(entry)) {
232
+ return {
233
+ valid: false,
234
+ error: `Content entry has been deleted: ${referenceId}`,
235
+ };
236
+ }
237
+
238
+ // If content type constraints specified, check them
239
+ if (allowedContentTypes && allowedContentTypes.length > 0) {
240
+ const contentType = await ctx.db.get(entry.contentTypeId);
241
+
242
+ if (!contentType) {
243
+ return {
244
+ valid: false,
245
+ error: `Content type not found for entry: ${referenceId}`,
246
+ };
247
+ }
248
+
249
+ if (!allowedContentTypes.includes(contentType.name)) {
250
+ return {
251
+ valid: false,
252
+ error: `Expected content type: ${allowedContentTypes.join(
253
+ " or ",
254
+ )}. Got: ${contentType.name}`,
255
+ };
256
+ }
257
+ }
258
+
259
+ return { valid: true };
260
+ } catch {
261
+ return {
262
+ valid: false,
263
+ error: `Invalid reference ID format: ${referenceId}`,
264
+ };
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Validate all references in a content entry's data.
270
+ *
271
+ * Iterates through all reference fields and validates that each reference
272
+ * points to a valid, existing content entry of the allowed type.
273
+ *
274
+ * @param ctx - Convex query context
275
+ * @param data - The content entry data containing reference fields
276
+ * @param fields - Array of field definitions (to identify reference fields)
277
+ * @returns Object with overall validity and array of errors
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * // Validate all references before creating/updating an entry:
282
+ * const validation = await validateAllReferences(ctx, data, contentType.fields);
283
+ * if (!validation.valid) {
284
+ * throw new Error(validation.errors.join(", "));
285
+ * }
286
+ * ```
287
+ */
288
+ export async function validateAllReferences(
289
+ ctx: QueryCtx,
290
+ data: Record<string, unknown>,
291
+ fields: Array<{
292
+ name: string;
293
+ type: string;
294
+ options?: {
295
+ allowedContentTypes?: string[];
296
+ multiple?: boolean;
297
+ };
298
+ }>,
299
+ ): Promise<{ valid: boolean; errors: string[] }> {
300
+ const errors: string[] = [];
301
+
302
+ // Find all reference fields
303
+ const referenceFields = fields.filter((f) => f.type === "reference");
304
+
305
+ for (const field of referenceFields) {
306
+ const value = data[field.name];
307
+
308
+ if (value === null || value === undefined) {
309
+ continue; // Skip empty values (required validation is separate)
310
+ }
311
+
312
+ const allowedTypes = field.options?.allowedContentTypes;
313
+ const multiple = field.options?.multiple ?? false;
314
+
315
+ if (multiple) {
316
+ // Validate array of references
317
+ if (!Array.isArray(value)) {
318
+ errors.push(`${field.name}: Expected array of references`);
319
+ continue;
320
+ }
321
+
322
+ for (const refId of value) {
323
+ if (typeof refId !== "string") {
324
+ errors.push(`${field.name}: Invalid reference ID type`);
325
+ continue;
326
+ }
327
+
328
+ const check = await isValidReference(ctx, refId, allowedTypes);
329
+ if (!check.valid) {
330
+ errors.push(`${field.name}: ${check.error}`);
331
+ }
332
+ }
333
+ } else {
334
+ // Validate single reference
335
+ if (typeof value !== "string") {
336
+ errors.push(`${field.name}: Expected string reference ID`);
337
+ continue;
338
+ }
339
+
340
+ const check = await isValidReference(ctx, value, allowedTypes);
341
+ if (!check.valid) {
342
+ errors.push(`${field.name}: ${check.error}`);
343
+ }
344
+ }
345
+ }
346
+
347
+ return {
348
+ valid: errors.length === 0,
349
+ errors,
350
+ };
351
+ }
352
+
353
+ // =============================================================================
354
+ // Utility Functions
355
+ // =============================================================================
356
+
357
+ /**
358
+ * Extract all reference IDs from a content entry's data.
359
+ *
360
+ * @param data - The content entry data
361
+ * @param fields - Array of field definitions
362
+ * @returns Array of all reference IDs found in the data
363
+ */
364
+ export function extractReferenceIds(
365
+ data: Record<string, unknown>,
366
+ fields: Array<{
367
+ name: string;
368
+ type: string;
369
+ options?: { multiple?: boolean };
370
+ }>,
371
+ ): string[] {
372
+ const ids: string[] = [];
373
+
374
+ const referenceFields = fields.filter((f) => f.type === "reference");
375
+
376
+ for (const field of referenceFields) {
377
+ const value = data[field.name];
378
+
379
+ if (value === null || value === undefined) {
380
+ continue;
381
+ }
382
+
383
+ const multiple = field.options?.multiple ?? false;
384
+
385
+ if (multiple && Array.isArray(value)) {
386
+ for (const id of value) {
387
+ if (typeof id === "string") {
388
+ ids.push(id);
389
+ }
390
+ }
391
+ } else if (typeof value === "string") {
392
+ ids.push(value);
393
+ }
394
+ }
395
+
396
+ return ids;
397
+ }
398
+
399
+ /**
400
+ * Get the content type name for a content entry ID.
401
+ *
402
+ * This is a helper function for the `validateReferenceContentType` function
403
+ * in the validation module.
404
+ *
405
+ * @param ctx - Convex query context
406
+ * @param entryId - The content entry ID
407
+ * @returns The content type name or null if not found
408
+ */
409
+ export async function getContentTypeName(
410
+ ctx: QueryCtx,
411
+ entryId: string,
412
+ ): Promise<string | null> {
413
+ try {
414
+ const entry = await ctx.db.get(entryId as Id<"contentEntries">);
415
+
416
+ if (!entry || isDeleted(entry)) {
417
+ return null;
418
+ }
419
+
420
+ const contentType = await ctx.db.get(entry.contentTypeId);
421
+
422
+ if (!contentType || isDeleted(contentType)) {
423
+ return null;
424
+ }
425
+
426
+ return contentType.name;
427
+ } catch {
428
+ return null;
429
+ }
430
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Slug Generator Utility
3
+ *
4
+ * Generates URL-friendly slugs from content titles.
5
+ * Handles special characters, unicode, and ensures slug format consistency.
6
+ */
7
+
8
+ /**
9
+ * Options for slug generation
10
+ */
11
+ export interface SlugOptions {
12
+ /** Maximum length of the generated slug (default: 100) */
13
+ maxLength?: number;
14
+ /** Separator character to use (default: '-') */
15
+ separator?: string;
16
+ /** Whether to lowercase the slug (default: true) */
17
+ lowercase?: boolean;
18
+ /** Custom replacements for specific characters */
19
+ customReplacements?: Record<string, string>;
20
+ }
21
+
22
+ /**
23
+ * Default character replacements for common special characters and unicode
24
+ */
25
+ const DEFAULT_REPLACEMENTS: Record<string, string> = {
26
+ // German
27
+ ä: "ae",
28
+ ö: "oe",
29
+ ü: "ue",
30
+ ß: "ss",
31
+ Ä: "ae",
32
+ Ö: "oe",
33
+ Ü: "ue",
34
+ // French
35
+ à: "a",
36
+ â: "a",
37
+ ç: "c",
38
+ é: "e",
39
+ è: "e",
40
+ ê: "e",
41
+ ë: "e",
42
+ î: "i",
43
+ ï: "i",
44
+ ô: "o",
45
+ ù: "u",
46
+ û: "u",
47
+ ÿ: "y",
48
+ œ: "oe",
49
+ æ: "ae",
50
+ // Spanish
51
+ ñ: "n",
52
+ Ñ: "n",
53
+ // Polish
54
+ ą: "a",
55
+ Ą: "a",
56
+ ć: "c",
57
+ Ć: "c",
58
+ ę: "e",
59
+ Ę: "e",
60
+ ł: "l",
61
+ Ł: "l",
62
+ ń: "n",
63
+ Ń: "n",
64
+ ó: "o",
65
+ Ó: "o",
66
+ ś: "s",
67
+ Ś: "s",
68
+ ź: "z",
69
+ Ź: "z",
70
+ ż: "z",
71
+ Ż: "z",
72
+ // Nordic
73
+ å: "a",
74
+ Å: "a",
75
+ ø: "o",
76
+ Ø: "o",
77
+ // Other common
78
+ ð: "d",
79
+ þ: "th",
80
+ // Symbols
81
+ "&": "and",
82
+ "@": "at",
83
+ "#": "hash",
84
+ "%": "percent",
85
+ "+": "plus",
86
+ "=": "equals",
87
+ // Punctuation to remove (replaced with empty string to prevent separator)
88
+ "'": "",
89
+ "\u2018": "", // left single quote '
90
+ "\u2019": "", // right single quote '
91
+ "\u201C": "", // left double quote "
92
+ "\u201D": "", // right double quote "
93
+ };
94
+
95
+ /**
96
+ * Generates a URL-friendly slug from a given title string.
97
+ *
98
+ * @param title - The input string to convert to a slug
99
+ * @param options - Optional configuration for slug generation
100
+ * @returns A URL-friendly slug string
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * generateSlug("Hello World!") // "hello-world"
105
+ * generateSlug("Café & Restaurant") // "cafe-and-restaurant"
106
+ * generateSlug("日本語タイトル") // "ri-ben-yu-taitoru"
107
+ * generateSlug(" Multiple Spaces ") // "multiple-spaces"
108
+ * ```
109
+ */
110
+ export function generateSlug(title: string, options: SlugOptions = {}): string {
111
+ const {
112
+ maxLength = 100,
113
+ separator = "-",
114
+ lowercase = true,
115
+ customReplacements = {},
116
+ } = options;
117
+
118
+ if (!title || typeof title !== "string") {
119
+ return "";
120
+ }
121
+
122
+ // Merge custom replacements with defaults (custom takes precedence)
123
+ const replacements = { ...DEFAULT_REPLACEMENTS, ...customReplacements };
124
+
125
+ let slug = title.trim();
126
+
127
+ // Apply character replacements
128
+ for (const [char, replacement] of Object.entries(replacements)) {
129
+ slug = slug.split(char).join(replacement);
130
+ }
131
+
132
+ // Normalize unicode to decomposed form, then remove combining diacritical marks
133
+ slug = slug.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
134
+
135
+ // Convert to lowercase if requested
136
+ if (lowercase) {
137
+ slug = slug.toLowerCase();
138
+ }
139
+
140
+ // Replace any non-alphanumeric characters (except separator) with separator
141
+ // This regex keeps letters (including unicode letters after normalization), numbers
142
+ const separatorRegex = new RegExp(`[^a-z0-9${escapeRegex(separator)}]`, "gi");
143
+ slug = slug.replace(separatorRegex, separator);
144
+
145
+ // Collapse multiple consecutive separators into one
146
+ const multipleSeparatorRegex = new RegExp(`${escapeRegex(separator)}+`, "g");
147
+ slug = slug.replace(multipleSeparatorRegex, separator);
148
+
149
+ // Remove leading and trailing separators
150
+ const trimSeparatorRegex = new RegExp(
151
+ `^${escapeRegex(separator)}|${escapeRegex(separator)}$`,
152
+ "g"
153
+ );
154
+ slug = slug.replace(trimSeparatorRegex, "");
155
+
156
+ // Truncate to max length, but don't cut words in the middle
157
+ if (slug.length > maxLength) {
158
+ slug = truncateSlug(slug, maxLength, separator);
159
+ }
160
+
161
+ return slug;
162
+ }
163
+
164
+ /**
165
+ * Escapes special regex characters in a string
166
+ */
167
+ function escapeRegex(str: string): string {
168
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
169
+ }
170
+
171
+ /**
172
+ * Truncates a slug to a maximum length without cutting words
173
+ */
174
+ function truncateSlug(
175
+ slug: string,
176
+ maxLength: number,
177
+ separator: string
178
+ ): string {
179
+ if (slug.length <= maxLength) {
180
+ return slug;
181
+ }
182
+
183
+ // Find the last separator before the max length
184
+ const truncated = slug.substring(0, maxLength);
185
+ const lastSeparatorIndex = truncated.lastIndexOf(separator);
186
+
187
+ if (lastSeparatorIndex > 0) {
188
+ return truncated.substring(0, lastSeparatorIndex);
189
+ }
190
+
191
+ // If no separator found, just truncate at maxLength
192
+ return truncated;
193
+ }
194
+
195
+ /**
196
+ * Validates if a string is a valid slug format
197
+ *
198
+ * @param slug - The string to validate
199
+ * @param separator - The separator character (default: '-')
200
+ * @returns True if the string is a valid slug
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * isValidSlug("hello-world") // true
205
+ * isValidSlug("Hello World") // false
206
+ * isValidSlug("hello--world") // false
207
+ * isValidSlug("-hello-world") // false
208
+ * ```
209
+ */
210
+ export function isValidSlug(slug: string, separator: string = "-"): boolean {
211
+ if (!slug || typeof slug !== "string") {
212
+ return false;
213
+ }
214
+
215
+ // Must be lowercase alphanumeric with single separators
216
+ const validSlugRegex = new RegExp(
217
+ `^[a-z0-9]+(?:${escapeRegex(separator)}[a-z0-9]+)*$`
218
+ );
219
+
220
+ return validSlugRegex.test(slug);
221
+ }
222
+
223
+ /**
224
+ * Generates a unique slug by appending a numeric suffix if needed.
225
+ * This is a helper that can be used with a uniqueness check function.
226
+ *
227
+ * @param baseSlug - The base slug to make unique
228
+ * @param isUnique - Async function that checks if a slug is unique
229
+ * @param maxAttempts - Maximum number of suffix attempts (default: 100)
230
+ * @returns A unique slug
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const checkUnique = async (slug: string) => {
235
+ * return !(await db.query("entries").withSlug(slug).first());
236
+ * };
237
+ * const uniqueSlug = await generateUniqueSlug("hello-world", checkUnique);
238
+ * // Returns "hello-world" if unique, or "hello-world-1", "hello-world-2", etc.
239
+ * ```
240
+ */
241
+ export async function generateUniqueSlug(
242
+ baseSlug: string,
243
+ isUnique: (slug: string) => Promise<boolean>,
244
+ maxAttempts: number = 100
245
+ ): Promise<string> {
246
+ // Check if base slug is already unique
247
+ if (await isUnique(baseSlug)) {
248
+ return baseSlug;
249
+ }
250
+
251
+ // Try appending numeric suffixes
252
+ for (let i = 1; i <= maxAttempts; i++) {
253
+ const candidateSlug = `${baseSlug}-${i}`;
254
+ if (await isUnique(candidateSlug)) {
255
+ return candidateSlug;
256
+ }
257
+ }
258
+
259
+ // Fallback: append timestamp if all numeric suffixes are taken
260
+ const timestamp = Date.now().toString(36);
261
+ return `${baseSlug}-${timestamp}`;
262
+ }