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,793 @@
1
+ /**
2
+ * Media Variants Query Functions
3
+ *
4
+ * Provides query functions for retrieving media variants (optimized versions
5
+ * of media assets like thumbnails, responsive sizes, and format conversions).
6
+ *
7
+ * Key features:
8
+ * - Get individual variants by ID
9
+ * - List all variants for a media asset
10
+ * - Find best matching variant for target dimensions
11
+ * - Generate responsive srcset data for HTML img tags
12
+ * - Support for variant presets (thumbnail, small, medium, large, etc.)
13
+ */
14
+
15
+ import { v, type Infer } from "convex/values";
16
+ import { isDeleted } from "./lib/softDelete.js";
17
+ import type { Id } from "./_generated/dataModel.js";
18
+ import { query } from "./_generated/server.js";
19
+ import {
20
+ mediaVariantDoc,
21
+ mediaVariantWithUrlDoc,
22
+ listMediaVariantsArgs,
23
+ getMediaVariantArgs,
24
+ getBestVariantArgs,
25
+ variantTypeValidator,
26
+ srcsetEntryValidator,
27
+ responsiveSrcsetResult,
28
+ } from "./validators.js";
29
+
30
+ /**
31
+ * Default variant presets for common use cases.
32
+ * These define standard sizes for responsive images and thumbnails.
33
+ */
34
+ export const DEFAULT_VARIANT_PRESETS = {
35
+ thumbnail: {
36
+ name: "thumbnail",
37
+ variantType: "thumbnail" as const,
38
+ width: 150,
39
+ height: 150,
40
+ format: "webp",
41
+ quality: 80,
42
+ description: "Small square thumbnail for previews and lists",
43
+ },
44
+ small: {
45
+ name: "small",
46
+ variantType: "responsive" as const,
47
+ width: 480,
48
+ format: "webp",
49
+ quality: 80,
50
+ description: "Small responsive image (480px wide)",
51
+ },
52
+ medium: {
53
+ name: "medium",
54
+ variantType: "responsive" as const,
55
+ width: 768,
56
+ format: "webp",
57
+ quality: 80,
58
+ description: "Medium responsive image (768px wide)",
59
+ },
60
+ large: {
61
+ name: "large",
62
+ variantType: "responsive" as const,
63
+ width: 1024,
64
+ format: "webp",
65
+ quality: 80,
66
+ description: "Large responsive image (1024px wide)",
67
+ },
68
+ xlarge: {
69
+ name: "xlarge",
70
+ variantType: "responsive" as const,
71
+ width: 1440,
72
+ format: "webp",
73
+ quality: 85,
74
+ description: "Extra large responsive image (1440px wide)",
75
+ },
76
+ webp: {
77
+ name: "webp",
78
+ variantType: "format" as const,
79
+ format: "webp",
80
+ quality: 85,
81
+ description: "WebP format conversion (same dimensions)",
82
+ },
83
+ avif: {
84
+ name: "avif",
85
+ variantType: "format" as const,
86
+ format: "avif",
87
+ quality: 80,
88
+ description: "AVIF format conversion (same dimensions)",
89
+ },
90
+ };
91
+
92
+ // =============================================================================
93
+ // Get Single Variant
94
+ // =============================================================================
95
+
96
+ /**
97
+ * Query to retrieve a single media variant by ID.
98
+ *
99
+ * Returns the variant metadata along with a resolved storage URL.
100
+ *
101
+ * @param id - The media variant ID to retrieve
102
+ * @param includeDeleted - Whether to include soft-deleted variants (default: false)
103
+ * @returns The media variant document with URL, or null if not found
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const variant = await ctx.runQuery(api.mediaVariants.get, {
108
+ * id: variantId,
109
+ * });
110
+ *
111
+ * if (variant && variant.status === "completed") {
112
+ * console.log("Variant URL:", variant.url);
113
+ * console.log("Dimensions:", variant.width, "x", variant.height);
114
+ * }
115
+ * ```
116
+ */
117
+ export const get = query({
118
+ args: getMediaVariantArgs.fields,
119
+ returns: v.union(mediaVariantWithUrlDoc, v.null()),
120
+ handler: async (ctx, args) => {
121
+ const { id, includeDeleted = false } = args;
122
+
123
+ const variant = await ctx.db.get(id);
124
+
125
+ if (!variant) {
126
+ return null;
127
+ }
128
+
129
+ // Filter out soft-deleted variants unless explicitly requested
130
+ if (!includeDeleted && isDeleted(variant)) {
131
+ return null;
132
+ }
133
+
134
+ // Resolve the storage URL
135
+ const url = await ctx.storage.getUrl(variant.storageId);
136
+
137
+ return {
138
+ ...variant,
139
+ url,
140
+ };
141
+ },
142
+ });
143
+
144
+ // =============================================================================
145
+ // List Variants for Asset
146
+ // =============================================================================
147
+
148
+ /**
149
+ * Query to list all variants for a media asset.
150
+ *
151
+ * Supports filtering by variant type, format, preset, and status.
152
+ *
153
+ * @param assetId - The parent media asset ID
154
+ * @param variantType - Filter by variant type (thumbnail, responsive, format)
155
+ * @param format - Filter by output format (webp, avif, jpeg, etc.)
156
+ * @param preset - Filter by preset name
157
+ * @param status - Filter by generation status
158
+ * @param includeDeleted - Include soft-deleted variants (default: false)
159
+ * @returns Array of media variant documents with URLs
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * // List all completed responsive variants
164
+ * const variants = await ctx.runQuery(api.mediaVariants.list, {
165
+ * assetId: assetId,
166
+ * variantType: "responsive",
167
+ * status: "completed",
168
+ * });
169
+ *
170
+ * // List all WebP variants
171
+ * const webpVariants = await ctx.runQuery(api.mediaVariants.list, {
172
+ * assetId: assetId,
173
+ * format: "webp",
174
+ * });
175
+ * ```
176
+ */
177
+ export const list = query({
178
+ args: listMediaVariantsArgs.fields,
179
+ returns: v.array(mediaVariantWithUrlDoc),
180
+ handler: async (ctx, args) => {
181
+ const {
182
+ assetId,
183
+ variantType,
184
+ format,
185
+ preset,
186
+ status,
187
+ includeDeleted = false,
188
+ } = args;
189
+
190
+ // Start with the base query using the asset index
191
+ let variants;
192
+
193
+ if (variantType) {
194
+ // Use the compound index for type filtering
195
+ variants = await ctx.db
196
+ .query("mediaVariants")
197
+ .withIndex("by_asset_and_type", (q) =>
198
+ q.eq("assetId", assetId).eq("variantType", variantType)
199
+ )
200
+ .collect();
201
+ } else if (preset) {
202
+ // Use the preset index
203
+ variants = await ctx.db
204
+ .query("mediaVariants")
205
+ .withIndex("by_asset_and_preset", (q) =>
206
+ q.eq("assetId", assetId).eq("preset", preset)
207
+ )
208
+ .collect();
209
+ } else if (format) {
210
+ // Use the format index
211
+ variants = await ctx.db
212
+ .query("mediaVariants")
213
+ .withIndex("by_asset_and_format", (q) =>
214
+ q.eq("assetId", assetId).eq("format", format)
215
+ )
216
+ .collect();
217
+ } else {
218
+ // Default: get all variants for the asset
219
+ variants = await ctx.db
220
+ .query("mediaVariants")
221
+ .withIndex("by_asset", (q) => q.eq("assetId", assetId))
222
+ .collect();
223
+ }
224
+
225
+ // Apply post-filters
226
+ let filteredVariants = variants;
227
+
228
+ // Filter by soft-delete status
229
+ if (!includeDeleted) {
230
+ filteredVariants = filteredVariants.filter(
231
+ (v) => !isDeleted(v)
232
+ );
233
+ }
234
+
235
+ // Apply additional filters that weren't covered by the index
236
+ if (format && !preset && variantType) {
237
+ filteredVariants = filteredVariants.filter((v) => v.format === format);
238
+ }
239
+ if (preset && !format && variantType) {
240
+ filteredVariants = filteredVariants.filter((v) => v.preset === preset);
241
+ }
242
+ if (status) {
243
+ filteredVariants = filteredVariants.filter((v) => v.status === status);
244
+ }
245
+
246
+ // Resolve URLs for all variants
247
+ const variantsWithUrls = await Promise.all(
248
+ filteredVariants.map(async (variant) => {
249
+ const url = await ctx.storage.getUrl(variant.storageId);
250
+ return {
251
+ ...variant,
252
+ url,
253
+ };
254
+ })
255
+ );
256
+
257
+ return variantsWithUrls;
258
+ },
259
+ });
260
+
261
+ // =============================================================================
262
+ // Get Best Matching Variant
263
+ // =============================================================================
264
+
265
+ /**
266
+ * Query to find the best matching variant for target dimensions.
267
+ *
268
+ * This is useful for serving appropriately sized images based on
269
+ * the display context (e.g., viewport width, container size).
270
+ *
271
+ * Selection logic:
272
+ * 1. Prefer variants matching the preferred format
273
+ * 2. Choose smallest variant that is >= target dimensions
274
+ * 3. If no variant is large enough, choose the largest available
275
+ * 4. Optionally fall back to original asset
276
+ *
277
+ * @param assetId - The parent media asset ID
278
+ * @param targetWidth - Target display width in pixels
279
+ * @param targetHeight - Target display height in pixels
280
+ * @param preferredFormat - Preferred format (e.g., "webp")
281
+ * @param fallbackToOriginal - Return original if no variant matches (default: true)
282
+ * @returns Best matching variant with URL, or null if none found
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * // Get best variant for a 400px wide container, preferring WebP
287
+ * const variant = await ctx.runQuery(api.mediaVariants.getBestVariant, {
288
+ * assetId: assetId,
289
+ * targetWidth: 400,
290
+ * preferredFormat: "webp",
291
+ * });
292
+ *
293
+ * if (variant) {
294
+ * console.log("Using variant:", variant.width, "x", variant.height);
295
+ * console.log("URL:", variant.url);
296
+ * }
297
+ * ```
298
+ */
299
+ export const getBestVariant = query({
300
+ args: getBestVariantArgs.fields,
301
+ returns: v.union(
302
+ v.object({
303
+ ...mediaVariantWithUrlDoc.fields,
304
+ isOriginal: v.boolean(),
305
+ }),
306
+ v.null()
307
+ ),
308
+ handler: async (ctx, args) => {
309
+ const {
310
+ assetId,
311
+ targetWidth,
312
+ targetHeight,
313
+ preferredFormat,
314
+ fallbackToOriginal = true,
315
+ } = args;
316
+
317
+ // Get all completed variants for the asset
318
+ const variants = await ctx.db
319
+ .query("mediaVariants")
320
+ .withIndex("by_asset", (q) => q.eq("assetId", assetId))
321
+ .filter((q) =>
322
+ q.and(
323
+ q.eq(q.field("status"), "completed"),
324
+ q.eq(q.field("deletedAt"), undefined)
325
+ )
326
+ )
327
+ .collect();
328
+
329
+ if (variants.length === 0) {
330
+ // No variants available, try fallback to original
331
+ if (fallbackToOriginal) {
332
+ const item = await ctx.db.get(assetId);
333
+ // Must be an asset (not folder) and not deleted
334
+ if (item && item.kind === "asset" && !isDeleted(item)) {
335
+ const asset = item;
336
+ const url = await ctx.storage.getUrl(asset.storageId);
337
+ return {
338
+ // When isOriginal=true, _id is actually an asset ID, not variant ID.
339
+ // Consumers should check isOriginal before using _id.
340
+ _id: asset._id as unknown as Id<"mediaVariants">,
341
+ _creationTime: asset._creationTime,
342
+ assetId: asset._id,
343
+ storageId: asset.storageId,
344
+ variantType: "format" as const,
345
+ width: asset.width,
346
+ height: asset.height,
347
+ format: getFormatFromMimeType(asset.mimeType),
348
+ mimeType: asset.mimeType,
349
+ size: asset.size ?? 0,
350
+ quality: undefined,
351
+ preset: undefined,
352
+ autoGenerated: false,
353
+ status: "completed" as const,
354
+ errorMessage: undefined,
355
+ processingStartedAt: undefined,
356
+ processingCompletedAt: undefined,
357
+ deletedAt: undefined,
358
+ createdBy: asset.createdBy,
359
+ url,
360
+ isOriginal: true,
361
+ };
362
+ }
363
+ }
364
+ return null;
365
+ }
366
+
367
+ // Score variants based on match quality
368
+ const scoredVariants = variants.map((variant) => {
369
+ let score = 0;
370
+
371
+ // Prefer matching format (+10 points)
372
+ if (preferredFormat && variant.format === preferredFormat) {
373
+ score += 10;
374
+ }
375
+
376
+ // Score based on size match
377
+ if (targetWidth && variant.width) {
378
+ if (variant.width >= targetWidth) {
379
+ // Variant is large enough
380
+ // Smaller oversizing is better (less wasted bandwidth)
381
+ const oversizeRatio = variant.width / targetWidth;
382
+ score += 5 - Math.min(4, oversizeRatio - 1); // 5 points for perfect match, down to 1
383
+ } else {
384
+ // Variant is too small, but still usable
385
+ const undersizeRatio = variant.width / targetWidth;
386
+ score += undersizeRatio * 2; // Up to 2 points based on how close
387
+ }
388
+ }
389
+
390
+ if (targetHeight && variant.height) {
391
+ if (variant.height >= targetHeight) {
392
+ const oversizeRatio = variant.height / targetHeight;
393
+ score += 5 - Math.min(4, oversizeRatio - 1);
394
+ } else {
395
+ const undersizeRatio = variant.height / targetHeight;
396
+ score += undersizeRatio * 2;
397
+ }
398
+ }
399
+
400
+ // Prefer smaller file sizes for equally scored variants
401
+ score -= variant.size / 1000000; // Subtract MB
402
+
403
+ return { variant, score };
404
+ });
405
+
406
+ // Sort by score (descending) and pick the best
407
+ scoredVariants.sort((a, b) => b.score - a.score);
408
+ const bestVariant = scoredVariants[0].variant;
409
+
410
+ const url = await ctx.storage.getUrl(bestVariant.storageId);
411
+
412
+ return {
413
+ ...bestVariant,
414
+ url,
415
+ isOriginal: false,
416
+ };
417
+ },
418
+ });
419
+
420
+ // =============================================================================
421
+ // Get Responsive Srcset
422
+ // =============================================================================
423
+
424
+ /**
425
+ * Query to generate responsive srcset data for HTML img/picture tags.
426
+ *
427
+ * Returns a complete srcset string and entries array for building
428
+ * responsive images with proper format support.
429
+ *
430
+ * @param assetId - The parent media asset ID
431
+ * @param format - Filter variants by format (optional)
432
+ * @returns Srcset data including src fallback, srcset string, and entries array
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * const srcsetData = await ctx.runQuery(api.mediaVariants.getResponsiveSrcset, {
437
+ * assetId: assetId,
438
+ * format: "webp",
439
+ * });
440
+ *
441
+ * // Use in HTML:
442
+ * // <img src={srcsetData.src} srcset={srcsetData.srcset} sizes="100vw" />
443
+ *
444
+ * // Or build a picture element:
445
+ * // <picture>
446
+ * // <source srcset={srcsetData.srcset} type="image/webp" />
447
+ * // <img src={srcsetData.src} />
448
+ * // </picture>
449
+ * ```
450
+ */
451
+ export const getResponsiveSrcset = query({
452
+ args: {
453
+ assetId: v.id("mediaItems"),
454
+ format: v.optional(v.string()),
455
+ },
456
+ returns: responsiveSrcsetResult,
457
+ handler: async (ctx, args) => {
458
+ const { assetId, format } = args;
459
+
460
+ // Get the original asset for fallback
461
+ const item = await ctx.db.get(assetId);
462
+ // Must be an asset (not folder) and not deleted
463
+ if (!item || item.kind !== "asset" || isDeleted(item)) {
464
+ return {
465
+ src: null,
466
+ srcset: "",
467
+ entries: [],
468
+ sizes: undefined,
469
+ };
470
+ }
471
+ const asset = item;
472
+
473
+ const originalUrl = await ctx.storage.getUrl(asset.storageId);
474
+
475
+ // Get all completed responsive variants
476
+ let variants;
477
+ if (format) {
478
+ variants = await ctx.db
479
+ .query("mediaVariants")
480
+ .withIndex("by_asset_and_format", (q) =>
481
+ q.eq("assetId", assetId).eq("format", format)
482
+ )
483
+ .filter((q) =>
484
+ q.and(
485
+ q.eq(q.field("status"), "completed"),
486
+ q.eq(q.field("deletedAt"), undefined),
487
+ q.or(
488
+ q.eq(q.field("variantType"), "responsive"),
489
+ q.eq(q.field("variantType"), "format")
490
+ )
491
+ )
492
+ )
493
+ .collect();
494
+ } else {
495
+ variants = await ctx.db
496
+ .query("mediaVariants")
497
+ .withIndex("by_asset", (q) => q.eq("assetId", assetId))
498
+ .filter((q) =>
499
+ q.and(
500
+ q.eq(q.field("status"), "completed"),
501
+ q.eq(q.field("deletedAt"), undefined),
502
+ q.or(
503
+ q.eq(q.field("variantType"), "responsive"),
504
+ q.eq(q.field("variantType"), "format")
505
+ )
506
+ )
507
+ )
508
+ .collect();
509
+ }
510
+
511
+ // Build srcset entries, filtering for variants with width
512
+ const entries: Infer<typeof srcsetEntryValidator>[] = [];
513
+
514
+ for (const variant of variants) {
515
+ if (variant.width) {
516
+ const url = await ctx.storage.getUrl(variant.storageId);
517
+ if (url) {
518
+ entries.push({
519
+ url,
520
+ descriptor: `${variant.width}w`,
521
+ width: variant.width,
522
+ format: variant.format,
523
+ });
524
+ }
525
+ }
526
+ }
527
+
528
+ // Sort by width ascending
529
+ entries.sort((a, b) => a.width - b.width);
530
+
531
+ // Build srcset string
532
+ const srcset = entries.map((e) => `${e.url} ${e.descriptor}`).join(", ");
533
+
534
+ // Generate a sizes hint based on available widths
535
+ let sizes: string | undefined;
536
+ if (entries.length > 0) {
537
+ const maxWidth = entries[entries.length - 1].width;
538
+ sizes = `(max-width: ${maxWidth}px) 100vw, ${maxWidth}px`;
539
+ }
540
+
541
+ return {
542
+ src: originalUrl,
543
+ srcset,
544
+ entries,
545
+ sizes,
546
+ };
547
+ },
548
+ });
549
+
550
+ // =============================================================================
551
+ // Get Variant Presets
552
+ // =============================================================================
553
+
554
+ /**
555
+ * Query to get available variant presets.
556
+ *
557
+ * Returns the default preset configurations that can be used
558
+ * when requesting variant generation.
559
+ *
560
+ * @returns Array of preset definitions
561
+ *
562
+ * @example
563
+ * ```typescript
564
+ * const presets = await ctx.runQuery(api.mediaVariants.getPresets);
565
+ *
566
+ * // Available presets: thumbnail, small, medium, large, xlarge, webp, avif
567
+ * for (const preset of presets) {
568
+ * console.log(`${preset.name}: ${preset.width}x${preset.height} ${preset.format}`);
569
+ * }
570
+ * ```
571
+ */
572
+ export const getPresets = query({
573
+ args: {},
574
+ returns: v.array(
575
+ v.object({
576
+ name: v.string(),
577
+ variantType: variantTypeValidator,
578
+ width: v.optional(v.number()),
579
+ height: v.optional(v.number()),
580
+ format: v.string(),
581
+ quality: v.optional(v.number()),
582
+ description: v.optional(v.string()),
583
+ })
584
+ ),
585
+ handler: async () => {
586
+ return Object.values(DEFAULT_VARIANT_PRESETS);
587
+ },
588
+ });
589
+
590
+ // =============================================================================
591
+ // Get Pending Variants (for processing queue)
592
+ // =============================================================================
593
+
594
+ /**
595
+ * Query to get variants that are pending or processing.
596
+ *
597
+ * Useful for monitoring the variant generation queue or
598
+ * building a processing system.
599
+ *
600
+ * @param status - Filter by status (pending or processing)
601
+ * @param limit - Maximum number of variants to return (default: 100)
602
+ * @returns Array of variants awaiting processing
603
+ *
604
+ * @example
605
+ * ```typescript
606
+ * // Get pending variants for processing
607
+ * const pending = await ctx.runQuery(api.mediaVariants.getPendingVariants, {
608
+ * status: "pending",
609
+ * limit: 10,
610
+ * });
611
+ *
612
+ * for (const variant of pending) {
613
+ * // Process variant...
614
+ * }
615
+ * ```
616
+ */
617
+ export const getPendingVariants = query({
618
+ args: {
619
+ status: v.optional(
620
+ v.union(v.literal("pending"), v.literal("processing"))
621
+ ),
622
+ limit: v.optional(v.number()),
623
+ },
624
+ returns: v.array(mediaVariantDoc),
625
+ handler: async (ctx, args) => {
626
+ const { status, limit = 100 } = args;
627
+
628
+ let variants;
629
+
630
+ if (status) {
631
+ variants = await ctx.db
632
+ .query("mediaVariants")
633
+ .withIndex("by_status", (q) => q.eq("status", status))
634
+ .order("asc") // Process oldest first
635
+ .take(limit);
636
+ } else {
637
+ // Get both pending and processing
638
+ const pending = await ctx.db
639
+ .query("mediaVariants")
640
+ .withIndex("by_status", (q) => q.eq("status", "pending"))
641
+ .order("asc")
642
+ .take(limit);
643
+
644
+ const processing = await ctx.db
645
+ .query("mediaVariants")
646
+ .withIndex("by_status", (q) => q.eq("status", "processing"))
647
+ .order("asc")
648
+ .take(limit);
649
+
650
+ variants = [...pending, ...processing].slice(0, limit);
651
+ }
652
+
653
+ return variants;
654
+ },
655
+ });
656
+
657
+ // =============================================================================
658
+ // Get Asset with Variants
659
+ // =============================================================================
660
+
661
+ /**
662
+ * Query to get a media asset with all its completed variants.
663
+ *
664
+ * Combines the original asset with all available variants
665
+ * for comprehensive media delivery.
666
+ *
667
+ * @param assetId - The media asset ID
668
+ * @returns Asset with variants and URLs
669
+ *
670
+ * @example
671
+ * ```typescript
672
+ * const assetWithVariants = await ctx.runQuery(api.mediaVariants.getAssetWithVariants, {
673
+ * assetId: assetId,
674
+ * });
675
+ *
676
+ * if (assetWithVariants) {
677
+ * console.log("Original:", assetWithVariants.original.url);
678
+ * console.log("Variants:", assetWithVariants.variants.length);
679
+ *
680
+ * // Find thumbnail
681
+ * const thumbnail = assetWithVariants.variants.find(v => v.preset === "thumbnail");
682
+ * }
683
+ * ```
684
+ */
685
+ export const getAssetWithVariants = query({
686
+ args: {
687
+ assetId: v.id("mediaItems"),
688
+ },
689
+ returns: v.union(
690
+ v.object({
691
+ original: v.object({
692
+ _id: v.id("mediaItems"),
693
+ _creationTime: v.number(),
694
+ name: v.string(),
695
+ mimeType: v.string(),
696
+ size: v.number(),
697
+ width: v.optional(v.number()),
698
+ height: v.optional(v.number()),
699
+ url: v.union(v.string(), v.null()),
700
+ }),
701
+ variants: v.array(mediaVariantWithUrlDoc),
702
+ variantsByType: v.object({
703
+ thumbnail: v.optional(mediaVariantWithUrlDoc),
704
+ responsive: v.array(mediaVariantWithUrlDoc),
705
+ format: v.array(mediaVariantWithUrlDoc),
706
+ }),
707
+ }),
708
+ v.null()
709
+ ),
710
+ handler: async (ctx, args) => {
711
+ const { assetId } = args;
712
+
713
+ // Get the original asset
714
+ const item = await ctx.db.get(assetId);
715
+ // Must be an asset (not folder) and not deleted
716
+ if (!item || item.kind !== "asset" || isDeleted(item)) {
717
+ return null;
718
+ }
719
+ const asset = item;
720
+
721
+ const originalUrl = await ctx.storage.getUrl(asset.storageId);
722
+
723
+ // Get all completed variants
724
+ const variants = await ctx.db
725
+ .query("mediaVariants")
726
+ .withIndex("by_asset", (q) => q.eq("assetId", assetId))
727
+ .filter((q) =>
728
+ q.and(
729
+ q.eq(q.field("status"), "completed"),
730
+ q.eq(q.field("deletedAt"), undefined)
731
+ )
732
+ )
733
+ .collect();
734
+
735
+ // Resolve URLs for all variants
736
+ const variantsWithUrls = await Promise.all(
737
+ variants.map(async (variant) => {
738
+ const url = await ctx.storage.getUrl(variant.storageId);
739
+ return { ...variant, url };
740
+ })
741
+ );
742
+
743
+ // Organize variants by type
744
+ const thumbnail = variantsWithUrls.find(
745
+ (v) => v.variantType === "thumbnail"
746
+ );
747
+ const responsive = variantsWithUrls
748
+ .filter((v) => v.variantType === "responsive")
749
+ .sort((a, b) => (a.width || 0) - (b.width || 0));
750
+ const formatVariants = variantsWithUrls.filter(
751
+ (v) => v.variantType === "format"
752
+ );
753
+
754
+ return {
755
+ original: {
756
+ _id: asset._id,
757
+ _creationTime: asset._creationTime,
758
+ name: asset.name,
759
+ mimeType: asset.mimeType,
760
+ size: asset.size ?? 0,
761
+ width: asset.width,
762
+ height: asset.height,
763
+ url: originalUrl,
764
+ },
765
+ variants: variantsWithUrls,
766
+ variantsByType: {
767
+ thumbnail,
768
+ responsive,
769
+ format: formatVariants,
770
+ },
771
+ };
772
+ },
773
+ });
774
+
775
+ // =============================================================================
776
+ // Helper Functions
777
+ // =============================================================================
778
+
779
+ /**
780
+ * Extract format string from MIME type.
781
+ */
782
+ function getFormatFromMimeType(mimeType: string): string {
783
+ const formatMap: Record<string, string> = {
784
+ "image/jpeg": "jpeg",
785
+ "image/jpg": "jpg",
786
+ "image/png": "png",
787
+ "image/webp": "webp",
788
+ "image/avif": "avif",
789
+ "image/gif": "gif",
790
+ "image/svg+xml": "svg",
791
+ };
792
+ return formatMap[mimeType] || mimeType.split("/")[1] || "unknown";
793
+ }