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,785 @@
1
+ /**
2
+ * Taxonomy Query Functions
3
+ *
4
+ * Provides query functions for retrieving taxonomy definitions and terms.
5
+ * Taxonomies are classification systems (like tags, categories, topics) that
6
+ * can be applied to content entries for organization and filtering.
7
+ *
8
+ * Available queries:
9
+ * - `get`: Retrieve a single taxonomy by ID or name
10
+ * - `list`: List all taxonomies with optional filtering
11
+ * - `getTerm`: Retrieve a single term by ID or slug
12
+ * - `listTerms`: List terms within a taxonomy with filtering and search
13
+ * - `getTermsByEntry`: Get all terms associated with a content entry
14
+ * - `getEntriesByTerm`: Get content entries associated with a term
15
+ * - `suggestTerms`: Get term suggestions based on partial input
16
+ */
17
+ import { v } from "convex/values";
18
+ import { isDeleted } from "./lib/softDelete.js";
19
+ import { paginationOptsValidator } from "convex/server";
20
+ import { query } from "./_generated/server.js";
21
+ import { taxonomyDoc, taxonomyTermDoc } from "./validators.js";
22
+ // =============================================================================
23
+ // Constants
24
+ // =============================================================================
25
+ const DEFAULT_NUM_ITEMS = 50;
26
+ const MAX_NUM_ITEMS = 250;
27
+ // =============================================================================
28
+ // Extended Validators
29
+ // =============================================================================
30
+ /**
31
+ * Term with children for hierarchical display.
32
+ * Extends the base taxonomyTermDoc with a children array.
33
+ */
34
+ const taxonomyTermWithChildren = v.object({
35
+ ...taxonomyTermDoc.fields,
36
+ children: v.array(v.any()), // Recursive type - will contain taxonomyTermWithChildren
37
+ });
38
+ // =============================================================================
39
+ // Get Taxonomy Query
40
+ // =============================================================================
41
+ /**
42
+ * Query to retrieve a single taxonomy by ID or name.
43
+ *
44
+ * @param id - The taxonomy ID for direct lookup (most efficient)
45
+ * @param name - The machine-readable name for index-based lookup
46
+ * @param includeDeleted - Whether to return soft-deleted taxonomies (default: false)
47
+ *
48
+ * @returns The taxonomy document, or null if not found
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * // Get by ID
53
+ * const taxonomy = await ctx.runQuery(api.taxonomies.get, { id: taxonomyId });
54
+ *
55
+ * // Get by name
56
+ * const tagsTaxonomy = await ctx.runQuery(api.taxonomies.get, { name: "tags" });
57
+ * ```
58
+ */
59
+ export const get = query({
60
+ args: {
61
+ id: v.optional(v.id("taxonomies")),
62
+ name: v.optional(v.string()),
63
+ includeDeleted: v.optional(v.boolean()),
64
+ },
65
+ returns: v.union(taxonomyDoc, v.null()),
66
+ handler: async (ctx, args) => {
67
+ const { id, name, includeDeleted = false } = args;
68
+ if (!id && !name) {
69
+ return null;
70
+ }
71
+ let taxonomy;
72
+ if (id) {
73
+ taxonomy = await ctx.db.get(id);
74
+ }
75
+ else if (name) {
76
+ taxonomy = await ctx.db
77
+ .query("taxonomies")
78
+ .withIndex("by_name", (q) => q.eq("name", name))
79
+ .first();
80
+ }
81
+ if (!taxonomy) {
82
+ return null;
83
+ }
84
+ if (!includeDeleted && isDeleted(taxonomy)) {
85
+ return null;
86
+ }
87
+ return taxonomy;
88
+ },
89
+ });
90
+ // =============================================================================
91
+ // List Taxonomies Query
92
+ // =============================================================================
93
+ /**
94
+ * Query to list all taxonomies with optional filtering.
95
+ *
96
+ * @param isActive - Filter by active status
97
+ * @param isHierarchical - Filter by hierarchical type
98
+ * @param includeDeleted - Whether to include soft-deleted taxonomies
99
+ * @param paginationOpts - Standard Convex pagination options
100
+ *
101
+ * @returns Paginated list of taxonomy documents
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * // List all active taxonomies
106
+ * const taxonomies = await ctx.runQuery(api.taxonomies.list, {
107
+ * isActive: true,
108
+ * paginationOpts: { numItems: 20 },
109
+ * });
110
+ *
111
+ * // List only flat taxonomies (like tags)
112
+ * const flatTaxonomies = await ctx.runQuery(api.taxonomies.list, {
113
+ * isHierarchical: false,
114
+ * paginationOpts: { numItems: 20 },
115
+ * });
116
+ * ```
117
+ */
118
+ export const list = query({
119
+ args: {
120
+ isActive: v.optional(v.boolean()),
121
+ isHierarchical: v.optional(v.boolean()),
122
+ includeDeleted: v.optional(v.boolean()),
123
+ paginationOpts: v.optional(paginationOptsValidator),
124
+ },
125
+ returns: v.object({
126
+ page: v.array(taxonomyDoc),
127
+ continueCursor: v.union(v.string(), v.null()),
128
+ isDone: v.boolean(),
129
+ }),
130
+ handler: async (ctx, args) => {
131
+ const { isActive, isHierarchical, includeDeleted = false, paginationOpts, } = args;
132
+ const numItems = paginationOpts
133
+ ? Math.min(Math.max(1, paginationOpts.numItems ?? DEFAULT_NUM_ITEMS), MAX_NUM_ITEMS)
134
+ : MAX_NUM_ITEMS;
135
+ let results;
136
+ if (isActive !== undefined) {
137
+ results = await ctx.db
138
+ .query("taxonomies")
139
+ .withIndex("by_active", (q) => q.eq("isActive", isActive))
140
+ .collect();
141
+ }
142
+ else {
143
+ results = await ctx.db.query("taxonomies").collect();
144
+ }
145
+ // Apply post-filters
146
+ if (!includeDeleted) {
147
+ results = results.filter((t) => !isDeleted(t));
148
+ }
149
+ if (isHierarchical !== undefined) {
150
+ results = results.filter((t) => t.isHierarchical === isHierarchical);
151
+ }
152
+ // Sort by sortOrder, then name
153
+ results.sort((a, b) => {
154
+ const orderA = a.sortOrder ?? 999;
155
+ const orderB = b.sortOrder ?? 999;
156
+ if (orderA !== orderB)
157
+ return orderA - orderB;
158
+ return a.name.localeCompare(b.name);
159
+ });
160
+ // Handle pagination
161
+ let startIndex = 0;
162
+ if (paginationOpts?.cursor) {
163
+ const cursorIndex = results.findIndex((t) => t._id === paginationOpts.cursor);
164
+ if (cursorIndex !== -1) {
165
+ startIndex = cursorIndex + 1;
166
+ }
167
+ }
168
+ const pageResults = results.slice(startIndex, startIndex + numItems + 1);
169
+ const isDone = pageResults.length <= numItems;
170
+ const page = isDone ? pageResults : pageResults.slice(0, numItems);
171
+ const continueCursor = !isDone && page.length > 0 ? page[page.length - 1]._id : null;
172
+ return { page, continueCursor, isDone };
173
+ },
174
+ });
175
+ // =============================================================================
176
+ // Get Term Query
177
+ // =============================================================================
178
+ /**
179
+ * Query to retrieve a single taxonomy term by ID or slug.
180
+ *
181
+ * @param id - The term ID for direct lookup
182
+ * @param taxonomyId - The taxonomy ID (required when looking up by slug)
183
+ * @param slug - The term slug for lookup within a taxonomy
184
+ * @param includeDeleted - Whether to return soft-deleted terms
185
+ *
186
+ * @returns The term document, or null if not found
187
+ */
188
+ export const getTerm = query({
189
+ args: {
190
+ id: v.optional(v.id("taxonomyTerms")),
191
+ taxonomyId: v.optional(v.id("taxonomies")),
192
+ slug: v.optional(v.string()),
193
+ includeDeleted: v.optional(v.boolean()),
194
+ },
195
+ returns: v.union(taxonomyTermDoc, v.null()),
196
+ handler: async (ctx, args) => {
197
+ const { id, taxonomyId, slug, includeDeleted = false } = args;
198
+ if (!id && (!taxonomyId || !slug)) {
199
+ return null;
200
+ }
201
+ let term;
202
+ if (id) {
203
+ term = await ctx.db.get(id);
204
+ }
205
+ else if (taxonomyId && slug) {
206
+ term = await ctx.db
207
+ .query("taxonomyTerms")
208
+ .withIndex("by_taxonomy_and_slug", (q) => q.eq("taxonomyId", taxonomyId).eq("slug", slug))
209
+ .first();
210
+ }
211
+ if (!term) {
212
+ return null;
213
+ }
214
+ if (!includeDeleted && isDeleted(term)) {
215
+ return null;
216
+ }
217
+ return term;
218
+ },
219
+ });
220
+ // =============================================================================
221
+ // List Terms Query
222
+ // =============================================================================
223
+ /**
224
+ * Query to list terms within a taxonomy.
225
+ *
226
+ * @param taxonomyId - The taxonomy to list terms from (required)
227
+ * @param parentId - Filter by parent term (for hierarchical navigation)
228
+ * @param rootOnly - Only return root-level terms (depth = 0)
229
+ * @param search - Search terms by name
230
+ * @param includeDeleted - Whether to include soft-deleted terms
231
+ * @param sortBy - Sort field: "name", "usageCount", "sortOrder"
232
+ * @param sortDirection - Sort direction
233
+ * @param paginationOpts - Standard Convex pagination options
234
+ *
235
+ * @returns Paginated list of term documents
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * // List all tags in a taxonomy
240
+ * const tags = await ctx.runQuery(api.taxonomies.listTerms, {
241
+ * taxonomyId: tagsTaxonomyId,
242
+ * paginationOpts: { numItems: 50 },
243
+ * });
244
+ *
245
+ * // List root categories only
246
+ * const rootCategories = await ctx.runQuery(api.taxonomies.listTerms, {
247
+ * taxonomyId: categoriesTaxonomyId,
248
+ * rootOnly: true,
249
+ * paginationOpts: { numItems: 20 },
250
+ * });
251
+ *
252
+ * // List children of a category
253
+ * const children = await ctx.runQuery(api.taxonomies.listTerms, {
254
+ * taxonomyId: categoriesTaxonomyId,
255
+ * parentId: parentCategoryId,
256
+ * paginationOpts: { numItems: 20 },
257
+ * });
258
+ *
259
+ * // Sort by popularity (usage count)
260
+ * const popularTags = await ctx.runQuery(api.taxonomies.listTerms, {
261
+ * taxonomyId: tagsTaxonomyId,
262
+ * sortBy: "usageCount",
263
+ * sortDirection: "desc",
264
+ * paginationOpts: { numItems: 20 },
265
+ * });
266
+ * ```
267
+ */
268
+ export const listTerms = query({
269
+ args: {
270
+ taxonomyId: v.id("taxonomies"),
271
+ parentId: v.optional(v.id("taxonomyTerms")),
272
+ rootOnly: v.optional(v.boolean()),
273
+ search: v.optional(v.string()),
274
+ includeDeleted: v.optional(v.boolean()),
275
+ sortBy: v.optional(v.union(v.literal("name"), v.literal("usageCount"), v.literal("sortOrder"))),
276
+ sortDirection: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
277
+ paginationOpts: v.optional(paginationOptsValidator),
278
+ },
279
+ returns: v.object({
280
+ page: v.array(taxonomyTermDoc),
281
+ continueCursor: v.union(v.string(), v.null()),
282
+ isDone: v.boolean(),
283
+ }),
284
+ handler: async (ctx, args) => {
285
+ const { taxonomyId, parentId, rootOnly, search, includeDeleted = false, sortBy = "name", sortDirection = "asc", paginationOpts, } = args;
286
+ const numItems = paginationOpts
287
+ ? Math.min(Math.max(1, paginationOpts.numItems ?? DEFAULT_NUM_ITEMS), MAX_NUM_ITEMS)
288
+ : MAX_NUM_ITEMS;
289
+ let results;
290
+ // Use search index if searching
291
+ if (search && search.trim().length > 0) {
292
+ results = await ctx.db
293
+ .query("taxonomyTerms")
294
+ .withSearchIndex("search_terms", (q) => q.search("searchText", search.trim()).eq("taxonomyId", taxonomyId))
295
+ .take(numItems * 4); // Fetch extra for post-filtering
296
+ }
297
+ else if (parentId !== undefined) {
298
+ // Filter by parent
299
+ results = await ctx.db
300
+ .query("taxonomyTerms")
301
+ .withIndex("by_parent", (q) => q.eq("parentId", parentId))
302
+ .collect();
303
+ // Additional filter for taxonomy (parent could be cross-taxonomy in theory)
304
+ results = results.filter((t) => t.taxonomyId === taxonomyId);
305
+ }
306
+ else {
307
+ // Get all terms in taxonomy
308
+ results = await ctx.db
309
+ .query("taxonomyTerms")
310
+ .withIndex("by_taxonomy", (q) => q.eq("taxonomyId", taxonomyId))
311
+ .collect();
312
+ }
313
+ // Apply post-filters
314
+ if (!includeDeleted) {
315
+ results = results.filter((t) => !isDeleted(t));
316
+ }
317
+ if (rootOnly) {
318
+ results = results.filter((t) => t.depth === 0);
319
+ }
320
+ // Sort results
321
+ results.sort((a, b) => {
322
+ let comparison = 0;
323
+ switch (sortBy) {
324
+ case "usageCount":
325
+ comparison = a.usageCount - b.usageCount;
326
+ break;
327
+ case "sortOrder":
328
+ comparison = (a.sortOrder ?? 999) - (b.sortOrder ?? 999);
329
+ break;
330
+ case "name":
331
+ default:
332
+ comparison = a.name.toLowerCase().localeCompare(b.name.toLowerCase());
333
+ break;
334
+ }
335
+ return sortDirection === "asc" ? comparison : -comparison;
336
+ });
337
+ // Handle pagination
338
+ let startIndex = 0;
339
+ if (paginationOpts?.cursor) {
340
+ const cursorIndex = results.findIndex((t) => t._id === paginationOpts.cursor);
341
+ if (cursorIndex !== -1) {
342
+ startIndex = cursorIndex + 1;
343
+ }
344
+ }
345
+ const pageResults = results.slice(startIndex, startIndex + numItems + 1);
346
+ const isDone = pageResults.length <= numItems;
347
+ const page = isDone ? pageResults : pageResults.slice(0, numItems);
348
+ const continueCursor = !isDone && page.length > 0 ? page[page.length - 1]._id : null;
349
+ return { page, continueCursor, isDone };
350
+ },
351
+ });
352
+ // =============================================================================
353
+ // Get Hierarchical Terms Query
354
+ // =============================================================================
355
+ /**
356
+ * Query to get all terms in a taxonomy as a hierarchical tree structure.
357
+ *
358
+ * This is useful for rendering nested category selectors or tree views.
359
+ * Returns terms with their children nested in a tree structure.
360
+ *
361
+ * @param taxonomyId - The taxonomy to get terms from
362
+ * @param includeDeleted - Whether to include soft-deleted terms
363
+ *
364
+ * @returns Array of root terms with nested children
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * const tree = await ctx.runQuery(api.taxonomies.getTermsHierarchy, {
369
+ * taxonomyId: categoriesTaxonomyId,
370
+ * });
371
+ * // Returns: [
372
+ * // { name: "Tech", children: [{ name: "Web Dev", children: [...] }] },
373
+ * // { name: "Design", children: [...] },
374
+ * // ]
375
+ * ```
376
+ */
377
+ export const getTermsHierarchy = query({
378
+ args: {
379
+ taxonomyId: v.id("taxonomies"),
380
+ includeDeleted: v.optional(v.boolean()),
381
+ },
382
+ returns: v.array(taxonomyTermWithChildren),
383
+ handler: async (ctx, args) => {
384
+ const { taxonomyId, includeDeleted = false } = args;
385
+ // Get all terms in the taxonomy
386
+ let terms = await ctx.db
387
+ .query("taxonomyTerms")
388
+ .withIndex("by_taxonomy", (q) => q.eq("taxonomyId", taxonomyId))
389
+ .collect();
390
+ // Filter deleted if needed
391
+ if (!includeDeleted) {
392
+ terms = terms.filter((t) => !isDeleted(t));
393
+ }
394
+ // Sort by sortOrder, then name
395
+ terms.sort((a, b) => {
396
+ const orderA = a.sortOrder ?? 999;
397
+ const orderB = b.sortOrder ?? 999;
398
+ if (orderA !== orderB)
399
+ return orderA - orderB;
400
+ return a.name.localeCompare(b.name);
401
+ });
402
+ // Build tree structure
403
+ const termMap = new Map();
404
+ const rootTerms = [];
405
+ // First pass: create term objects with empty children
406
+ for (const term of terms) {
407
+ termMap.set(term._id, { ...term, children: [] });
408
+ }
409
+ // Second pass: link parents to children
410
+ for (const term of terms) {
411
+ const termWithChildren = termMap.get(term._id);
412
+ if (term.parentId && termMap.has(term.parentId)) {
413
+ const parent = termMap.get(term.parentId);
414
+ parent.children.push(termWithChildren);
415
+ }
416
+ else {
417
+ rootTerms.push(termWithChildren);
418
+ }
419
+ }
420
+ return rootTerms;
421
+ },
422
+ });
423
+ // =============================================================================
424
+ // Suggest Terms Query
425
+ // =============================================================================
426
+ /**
427
+ * Query to get term suggestions based on partial input.
428
+ *
429
+ * This is useful for autocomplete functionality when users are selecting
430
+ * or creating tags. Returns matching terms sorted by relevance and usage.
431
+ *
432
+ * @param taxonomyId - The taxonomy to search within
433
+ * @param query - The partial input to match against term names
434
+ * @param limit - Maximum number of suggestions to return (default: 10)
435
+ * @param excludeIds - Term IDs to exclude from suggestions (already selected)
436
+ *
437
+ * @returns Array of matching terms
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * const suggestions = await ctx.runQuery(api.taxonomies.suggestTerms, {
442
+ * taxonomyId: tagsTaxonomyId,
443
+ * query: "java",
444
+ * limit: 5,
445
+ * excludeIds: alreadySelectedTagIds,
446
+ * });
447
+ * // Returns: [{ name: "JavaScript" }, { name: "Java" }, { name: "JavaFX" }]
448
+ * ```
449
+ */
450
+ export const suggestTerms = query({
451
+ args: {
452
+ taxonomyId: v.id("taxonomies"),
453
+ query: v.string(),
454
+ limit: v.optional(v.number()),
455
+ excludeIds: v.optional(v.array(v.id("taxonomyTerms"))),
456
+ },
457
+ returns: v.array(taxonomyTermDoc),
458
+ handler: async (ctx, args) => {
459
+ const { taxonomyId, query: searchQuery, limit = 10, excludeIds = [], } = args;
460
+ const excludeSet = new Set(excludeIds);
461
+ if (!searchQuery || searchQuery.trim().length === 0) {
462
+ // Return popular terms if no query
463
+ const terms = await ctx.db
464
+ .query("taxonomyTerms")
465
+ .withIndex("by_taxonomy_and_usage", (q) => q.eq("taxonomyId", taxonomyId))
466
+ .order("desc")
467
+ .take(limit * 2);
468
+ return terms
469
+ .filter((t) => !isDeleted(t) && !excludeSet.has(t._id))
470
+ .slice(0, limit);
471
+ }
472
+ // Search for matching terms
473
+ const terms = await ctx.db
474
+ .query("taxonomyTerms")
475
+ .withSearchIndex("search_terms", (q) => q.search("searchText", searchQuery.trim()).eq("taxonomyId", taxonomyId))
476
+ .take(limit * 2);
477
+ // Filter and limit
478
+ const filtered = terms.filter((t) => !isDeleted(t) && !excludeSet.has(t._id));
479
+ // Sort by: exact prefix match first, then usage count
480
+ const query = searchQuery.toLowerCase();
481
+ filtered.sort((a, b) => {
482
+ const aExact = a.name.toLowerCase().startsWith(query) ? 0 : 1;
483
+ const bExact = b.name.toLowerCase().startsWith(query) ? 0 : 1;
484
+ if (aExact !== bExact)
485
+ return aExact - bExact;
486
+ return b.usageCount - a.usageCount;
487
+ });
488
+ return filtered.slice(0, limit);
489
+ },
490
+ });
491
+ // =============================================================================
492
+ // Get Terms by Entry Query
493
+ // =============================================================================
494
+ /**
495
+ * Query to get all taxonomy terms associated with a content entry.
496
+ *
497
+ * @param entryId - The content entry ID
498
+ * @param taxonomyId - Optional taxonomy filter
499
+ * @param fieldName - Optional field name filter
500
+ *
501
+ * @returns Array of terms associated with the entry
502
+ *
503
+ * @example
504
+ * ```typescript
505
+ * // Get all tags for an entry
506
+ * const entryTags = await ctx.runQuery(api.taxonomies.getTermsByEntry, {
507
+ * entryId: blogPostId,
508
+ * });
509
+ *
510
+ * // Get only tags from a specific field
511
+ * const primaryTags = await ctx.runQuery(api.taxonomies.getTermsByEntry, {
512
+ * entryId: blogPostId,
513
+ * fieldName: "tags",
514
+ * });
515
+ * ```
516
+ */
517
+ export const getTermsByEntry = query({
518
+ args: {
519
+ entryId: v.id("contentEntries"),
520
+ taxonomyId: v.optional(v.id("taxonomies")),
521
+ fieldName: v.optional(v.string()),
522
+ },
523
+ returns: v.array(v.object({
524
+ ...taxonomyTermDoc.fields,
525
+ fieldName: v.string(),
526
+ sortOrder: v.optional(v.number()),
527
+ })),
528
+ handler: async (ctx, args) => {
529
+ const { entryId, taxonomyId, fieldName } = args;
530
+ // Get the junction table entries
531
+ const junctionQuery = ctx.db
532
+ .query("contentEntryTags")
533
+ .withIndex("by_entry", (q) => q.eq("entryId", entryId));
534
+ const junctionEntries = await junctionQuery.collect();
535
+ // Filter by taxonomy or field if specified
536
+ let filtered = junctionEntries;
537
+ if (taxonomyId) {
538
+ filtered = filtered.filter((j) => j.taxonomyId === taxonomyId);
539
+ }
540
+ if (fieldName) {
541
+ filtered = filtered.filter((j) => j.fieldName === fieldName);
542
+ }
543
+ // Sort by sortOrder
544
+ filtered.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
545
+ // Fetch the actual terms
546
+ const results = [];
547
+ for (const junction of filtered) {
548
+ const term = await ctx.db.get(junction.termId);
549
+ if (term && !isDeleted(term)) {
550
+ results.push({
551
+ ...term,
552
+ fieldName: junction.fieldName,
553
+ sortOrder: junction.sortOrder,
554
+ });
555
+ }
556
+ }
557
+ return results;
558
+ },
559
+ });
560
+ // =============================================================================
561
+ // Get Entries by Term Query
562
+ // =============================================================================
563
+ /**
564
+ * Query to get content entries that have a specific term.
565
+ *
566
+ * @param termId - The term ID to search for
567
+ * @param status - Optional entry status filter
568
+ * @param paginationOpts - Standard Convex pagination options
569
+ *
570
+ * @returns Paginated list of entry IDs with the term
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * // Get all entries with a specific tag
575
+ * const entriesWithTag = await ctx.runQuery(api.taxonomies.getEntriesByTerm, {
576
+ * termId: javascriptTagId,
577
+ * status: "published",
578
+ * paginationOpts: { numItems: 20 },
579
+ * });
580
+ * ```
581
+ */
582
+ export const getEntriesByTerm = query({
583
+ args: {
584
+ termId: v.id("taxonomyTerms"),
585
+ status: v.optional(v.union(v.literal("draft"), v.literal("published"), v.literal("archived"), v.literal("scheduled"))),
586
+ paginationOpts: v.optional(paginationOptsValidator),
587
+ },
588
+ returns: v.object({
589
+ page: v.array(v.id("contentEntries")),
590
+ continueCursor: v.union(v.string(), v.null()),
591
+ isDone: v.boolean(),
592
+ }),
593
+ handler: async (ctx, args) => {
594
+ const { termId, status, paginationOpts } = args;
595
+ const numItems = paginationOpts
596
+ ? Math.min(Math.max(1, paginationOpts.numItems ?? DEFAULT_NUM_ITEMS), MAX_NUM_ITEMS)
597
+ : DEFAULT_NUM_ITEMS;
598
+ // Get junction entries for this term
599
+ const junctionEntries = await ctx.db
600
+ .query("contentEntryTags")
601
+ .withIndex("by_term", (q) => q.eq("termId", termId))
602
+ .collect();
603
+ // Get unique entry IDs
604
+ const entryIds = [...new Set(junctionEntries.map((j) => j.entryId))];
605
+ // Filter by status if needed
606
+ let filteredEntryIds = entryIds;
607
+ if (status) {
608
+ const validEntryIds = [];
609
+ for (const entryId of entryIds) {
610
+ const entry = await ctx.db.get(entryId);
611
+ if (entry && entry.status === status && !isDeleted(entry)) {
612
+ validEntryIds.push(entryId);
613
+ }
614
+ }
615
+ filteredEntryIds = validEntryIds;
616
+ }
617
+ // Handle pagination
618
+ let startIndex = 0;
619
+ if (paginationOpts?.cursor) {
620
+ const cursorIndex = filteredEntryIds.findIndex((id) => id === paginationOpts.cursor);
621
+ if (cursorIndex !== -1) {
622
+ startIndex = cursorIndex + 1;
623
+ }
624
+ }
625
+ const pageResults = filteredEntryIds.slice(startIndex, startIndex + numItems + 1);
626
+ const isDone = pageResults.length <= numItems;
627
+ const page = isDone ? pageResults : pageResults.slice(0, numItems);
628
+ const continueCursor = !isDone && page.length > 0 ? page[page.length - 1] : null;
629
+ return { page, continueCursor, isDone };
630
+ },
631
+ });
632
+ // =============================================================================
633
+ // Count Terms Query
634
+ // =============================================================================
635
+ /**
636
+ * Query to count terms in a taxonomy.
637
+ *
638
+ * @param taxonomyId - The taxonomy to count terms in
639
+ * @param includeDeleted - Whether to include soft-deleted terms
640
+ *
641
+ * @returns Object containing the count
642
+ */
643
+ export const countTerms = query({
644
+ args: {
645
+ taxonomyId: v.id("taxonomies"),
646
+ includeDeleted: v.optional(v.boolean()),
647
+ },
648
+ returns: v.object({
649
+ count: v.number(),
650
+ }),
651
+ handler: async (ctx, args) => {
652
+ const { taxonomyId, includeDeleted = false } = args;
653
+ const terms = await ctx.db
654
+ .query("taxonomyTerms")
655
+ .withIndex("by_taxonomy", (q) => q.eq("taxonomyId", taxonomyId))
656
+ .collect();
657
+ const filteredTerms = includeDeleted
658
+ ? terms
659
+ : terms.filter((t) => !isDeleted(t));
660
+ return { count: filteredTerms.length };
661
+ },
662
+ });
663
+ // =============================================================================
664
+ // Media Asset Taxonomy Queries
665
+ // =============================================================================
666
+ /**
667
+ * Query to get all taxonomy terms associated with a media asset.
668
+ *
669
+ * @param mediaId - The media asset ID
670
+ * @param taxonomyId - Optional taxonomy filter
671
+ *
672
+ * @returns Array of terms associated with the media asset
673
+ *
674
+ * @example
675
+ * ```typescript
676
+ * // Get all terms for a media asset
677
+ * const mediaTags = await ctx.runQuery(api.taxonomies.getTermsByMedia, {
678
+ * mediaId: imageId,
679
+ * });
680
+ *
681
+ * // Get only terms from a specific taxonomy
682
+ * const categories = await ctx.runQuery(api.taxonomies.getTermsByMedia, {
683
+ * mediaId: imageId,
684
+ * taxonomyId: categoriesTaxonomyId,
685
+ * });
686
+ * ```
687
+ */
688
+ export const getTermsByMedia = query({
689
+ args: {
690
+ mediaId: v.id("mediaItems"),
691
+ taxonomyId: v.optional(v.id("taxonomies")),
692
+ },
693
+ returns: v.array(v.object({
694
+ ...taxonomyTermDoc.fields,
695
+ sortOrder: v.optional(v.number()),
696
+ })),
697
+ handler: async (ctx, args) => {
698
+ const { mediaId, taxonomyId } = args;
699
+ const junctionEntries = await ctx.db
700
+ .query("mediaAssetTags")
701
+ .withIndex("by_media", (q) => q.eq("mediaId", mediaId))
702
+ .collect();
703
+ let filtered = junctionEntries;
704
+ if (taxonomyId) {
705
+ filtered = filtered.filter((j) => j.taxonomyId === taxonomyId);
706
+ }
707
+ filtered.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
708
+ const results = [];
709
+ for (const junction of filtered) {
710
+ const term = await ctx.db.get(junction.termId);
711
+ if (term && !isDeleted(term)) {
712
+ results.push({
713
+ ...term,
714
+ sortOrder: junction.sortOrder,
715
+ });
716
+ }
717
+ }
718
+ return results;
719
+ },
720
+ });
721
+ /**
722
+ * Query to get media assets that have a specific term.
723
+ *
724
+ * @param termId - The term ID to search for
725
+ * @param includeDeleted - Whether to include soft-deleted media
726
+ * @param paginationOpts - Standard Convex pagination options
727
+ *
728
+ * @returns Paginated list of media asset IDs with the term
729
+ *
730
+ * @example
731
+ * ```typescript
732
+ * // Get all media with a specific category
733
+ * const mediaWithCategory = await ctx.runQuery(api.taxonomies.getMediaByTerm, {
734
+ * termId: landscapeCategoryId,
735
+ * paginationOpts: { numItems: 20 },
736
+ * });
737
+ * ```
738
+ */
739
+ export const getMediaByTerm = query({
740
+ args: {
741
+ termId: v.id("taxonomyTerms"),
742
+ includeDeleted: v.optional(v.boolean()),
743
+ paginationOpts: v.optional(paginationOptsValidator),
744
+ },
745
+ returns: v.object({
746
+ page: v.array(v.id("mediaItems")),
747
+ continueCursor: v.union(v.string(), v.null()),
748
+ isDone: v.boolean(),
749
+ }),
750
+ handler: async (ctx, args) => {
751
+ const { termId, includeDeleted = false, paginationOpts } = args;
752
+ const numItems = paginationOpts
753
+ ? Math.min(Math.max(1, paginationOpts.numItems ?? DEFAULT_NUM_ITEMS), MAX_NUM_ITEMS)
754
+ : DEFAULT_NUM_ITEMS;
755
+ const junctionEntries = await ctx.db
756
+ .query("mediaAssetTags")
757
+ .withIndex("by_term", (q) => q.eq("termId", termId))
758
+ .collect();
759
+ const mediaIds = [...new Set(junctionEntries.map((j) => j.mediaId))];
760
+ let filteredMediaIds = mediaIds;
761
+ if (!includeDeleted) {
762
+ const validMediaIds = [];
763
+ for (const mediaId of mediaIds) {
764
+ const media = await ctx.db.get(mediaId);
765
+ if (media && !isDeleted(media)) {
766
+ validMediaIds.push(mediaId);
767
+ }
768
+ }
769
+ filteredMediaIds = validMediaIds;
770
+ }
771
+ let startIndex = 0;
772
+ if (paginationOpts?.cursor) {
773
+ const cursorIndex = filteredMediaIds.findIndex((id) => id === paginationOpts.cursor);
774
+ if (cursorIndex !== -1) {
775
+ startIndex = cursorIndex + 1;
776
+ }
777
+ }
778
+ const pageResults = filteredMediaIds.slice(startIndex, startIndex + numItems + 1);
779
+ const isDone = pageResults.length <= numItems;
780
+ const page = isDone ? pageResults : pageResults.slice(0, numItems);
781
+ const continueCursor = !isDone && page.length > 0 ? page[page.length - 1] : null;
782
+ return { page, continueCursor, isDone };
783
+ },
784
+ });
785
+ //# sourceMappingURL=taxonomies.js.map