convex-cms 0.0.2 → 0.0.3

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 (265) hide show
  1. package/admin-dist/nitro.json +15 -0
  2. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  3. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  4. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  5. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  6. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  7. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  8. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  9. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  10. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  11. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  12. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  13. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  14. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  15. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  16. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  17. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  18. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  19. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  20. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  21. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  22. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  23. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  24. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  25. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  26. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  27. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  28. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  29. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  30. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  31. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  32. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  33. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  34. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  35. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  36. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  37. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  38. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  39. package/admin-dist/public/favicon.ico +0 -0
  40. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  41. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  42. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  45. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  46. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  83. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  84. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  88. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  89. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  90. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  91. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  92. package/admin-dist/server/_libs/clsx.mjs +16 -0
  93. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  94. package/admin-dist/server/_libs/convex.mjs +4841 -0
  95. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  96. package/admin-dist/server/_libs/croner.mjs +1 -0
  97. package/admin-dist/server/_libs/crossws.mjs +1 -0
  98. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  99. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  100. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  101. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  102. package/admin-dist/server/_libs/h3.mjs +401 -0
  103. package/admin-dist/server/_libs/hookable.mjs +1 -0
  104. package/admin-dist/server/_libs/isbot.mjs +20 -0
  105. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  106. package/admin-dist/server/_libs/ohash.mjs +1 -0
  107. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  108. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  109. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  110. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  111. package/admin-dist/server/_libs/rou3.mjs +8 -0
  112. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  113. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  114. package/admin-dist/server/_libs/srvx.mjs +719 -0
  115. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  116. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  117. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  118. package/admin-dist/server/_libs/tslib.mjs +39 -0
  119. package/admin-dist/server/_libs/ufo.mjs +54 -0
  120. package/admin-dist/server/_libs/unctx.mjs +1 -0
  121. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  122. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  123. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  124. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  125. package/admin-dist/server/_libs/zod.mjs +4223 -0
  126. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  127. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  128. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  129. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  130. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  131. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  132. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  133. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  134. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  135. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  136. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  137. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  138. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  139. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  140. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  141. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  142. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  143. package/admin-dist/server/_ssr/index.mjs +1275 -0
  144. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  145. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  146. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  147. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  148. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  149. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  150. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  151. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  152. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  153. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  154. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  155. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  156. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  157. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  158. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  159. package/admin-dist/server/favicon.ico +0 -0
  160. package/admin-dist/server/index.mjs +627 -0
  161. package/dist/cli/index.js +0 -0
  162. package/dist/client/admin-config.d.ts +0 -1
  163. package/dist/client/admin-config.d.ts.map +1 -1
  164. package/dist/client/admin-config.js +0 -1
  165. package/dist/client/admin-config.js.map +1 -1
  166. package/dist/client/adminApi.d.ts.map +1 -1
  167. package/dist/client/agentTools.d.ts +1237 -135
  168. package/dist/client/agentTools.d.ts.map +1 -1
  169. package/dist/client/agentTools.js +33 -9
  170. package/dist/client/agentTools.js.map +1 -1
  171. package/dist/client/index.d.ts +1 -1
  172. package/dist/client/index.d.ts.map +1 -1
  173. package/dist/client/index.js.map +1 -1
  174. package/dist/component/_generated/component.d.ts +9 -0
  175. package/dist/component/_generated/component.d.ts.map +1 -1
  176. package/dist/component/mediaAssets.d.ts +35 -0
  177. package/dist/component/mediaAssets.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.js +81 -0
  179. package/dist/component/mediaAssets.js.map +1 -1
  180. package/dist/test.d.ts.map +1 -1
  181. package/dist/test.js +2 -1
  182. package/dist/test.js.map +1 -1
  183. package/package.json +9 -5
  184. package/dist/component/auditLog.d.ts +0 -410
  185. package/dist/component/auditLog.d.ts.map +0 -1
  186. package/dist/component/auditLog.js +0 -607
  187. package/dist/component/auditLog.js.map +0 -1
  188. package/dist/component/types.d.ts +0 -4
  189. package/dist/component/types.d.ts.map +0 -1
  190. package/dist/component/types.js +0 -2
  191. package/dist/component/types.js.map +0 -1
  192. package/src/cli/commands/admin.ts +0 -104
  193. package/src/cli/index.ts +0 -21
  194. package/src/cli/utils/detectConvexUrl.ts +0 -54
  195. package/src/cli/utils/openBrowser.ts +0 -16
  196. package/src/client/admin-config.ts +0 -138
  197. package/src/client/adminApi.ts +0 -942
  198. package/src/client/agentTools.ts +0 -1311
  199. package/src/client/argTypes.ts +0 -316
  200. package/src/client/field-types.ts +0 -187
  201. package/src/client/index.ts +0 -1301
  202. package/src/client/queryBuilder.ts +0 -1100
  203. package/src/client/schema/codegen.ts +0 -500
  204. package/src/client/schema/defineContentType.ts +0 -501
  205. package/src/client/schema/index.ts +0 -169
  206. package/src/client/schema/schemaDrift.ts +0 -574
  207. package/src/client/schema/typedClient.ts +0 -688
  208. package/src/client/schema/types.ts +0 -666
  209. package/src/client/types.ts +0 -723
  210. package/src/client/workflows.ts +0 -141
  211. package/src/client/wrapper.ts +0 -4304
  212. package/src/component/_generated/api.ts +0 -140
  213. package/src/component/_generated/component.ts +0 -5029
  214. package/src/component/_generated/dataModel.ts +0 -60
  215. package/src/component/_generated/server.ts +0 -156
  216. package/src/component/authorization.ts +0 -647
  217. package/src/component/authorizationHooks.ts +0 -668
  218. package/src/component/bulkOperations.ts +0 -687
  219. package/src/component/contentEntries.ts +0 -1976
  220. package/src/component/contentEntryMutations.ts +0 -1223
  221. package/src/component/contentEntryValidation.ts +0 -707
  222. package/src/component/contentLock.ts +0 -550
  223. package/src/component/contentTypeMigration.ts +0 -1064
  224. package/src/component/contentTypeMutations.ts +0 -969
  225. package/src/component/contentTypes.ts +0 -346
  226. package/src/component/convex.config.ts +0 -44
  227. package/src/component/documentTypes.ts +0 -240
  228. package/src/component/eventEmitter.ts +0 -485
  229. package/src/component/exportImport.ts +0 -1169
  230. package/src/component/index.ts +0 -491
  231. package/src/component/lib/deepReferenceResolver.ts +0 -999
  232. package/src/component/lib/errors.ts +0 -816
  233. package/src/component/lib/index.ts +0 -145
  234. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  235. package/src/component/lib/metadataExtractor.ts +0 -792
  236. package/src/component/lib/mutationAuth.ts +0 -199
  237. package/src/component/lib/queries.ts +0 -79
  238. package/src/component/lib/ragContentChunker.ts +0 -1371
  239. package/src/component/lib/referenceResolver.ts +0 -430
  240. package/src/component/lib/slugGenerator.ts +0 -262
  241. package/src/component/lib/slugUniqueness.ts +0 -333
  242. package/src/component/lib/softDelete.ts +0 -44
  243. package/src/component/localeFallbackChain.ts +0 -673
  244. package/src/component/localeFields.ts +0 -896
  245. package/src/component/mediaAssetMutations.ts +0 -725
  246. package/src/component/mediaAssets.ts +0 -932
  247. package/src/component/mediaFolderMutations.ts +0 -1046
  248. package/src/component/mediaUploadMutations.ts +0 -224
  249. package/src/component/mediaVariantMutations.ts +0 -900
  250. package/src/component/mediaVariants.ts +0 -793
  251. package/src/component/ragContentIndexer.ts +0 -1067
  252. package/src/component/rateLimitHooks.ts +0 -572
  253. package/src/component/roles.ts +0 -1360
  254. package/src/component/scheduledPublish.ts +0 -358
  255. package/src/component/schema.ts +0 -617
  256. package/src/component/taxonomies.ts +0 -949
  257. package/src/component/taxonomyMutations.ts +0 -1210
  258. package/src/component/trash.ts +0 -724
  259. package/src/component/userContext.ts +0 -898
  260. package/src/component/validation.ts +0 -1388
  261. package/src/component/validators.ts +0 -949
  262. package/src/component/versionMutations.ts +0 -392
  263. package/src/component/webhookTrigger.ts +0 -1922
  264. package/src/react/index.ts +0 -898
  265. package/src/test.ts +0 -1580
@@ -1,900 +0,0 @@
1
- /**
2
- * Media Variant Mutation Functions
3
- *
4
- * Provides mutation functions for creating, updating, and deleting media variants.
5
- * Variants are optimized versions of media assets (thumbnails, responsive sizes,
6
- * format conversions).
7
- *
8
- * Key operations:
9
- * - Create variant: Register a completed variant after external processing
10
- * - Request generation: Queue a variant for async processing
11
- * - Update status: Update processing status (for job queue integration)
12
- * - Delete variant: Soft or hard delete variants
13
- * - Bulk operations: Generate multiple variants from presets
14
- */
15
-
16
- import { v } from "convex/values";
17
- import { isDeleted } from "./lib/softDelete.js";
18
- import type { Id } from "./_generated/dataModel.js";
19
- import { mutation, internalMutation } from "./_generated/server.js";
20
- import {
21
- createMediaVariantArgs,
22
- requestVariantGenerationArgs,
23
- updateVariantStatusArgs,
24
- deleteMediaVariantArgs,
25
- deleteAssetVariantsArgs,
26
- mediaVariantDoc,
27
- mediaVariantWithUrlDoc,
28
- // variantTypeValidator,
29
- // variantStatusValidator,
30
- generateVariantsResult,
31
- } from "./validators.js";
32
- import { classifyMimeType } from "./lib/metadataExtractor.js";
33
- import { emitEvent } from "./eventEmitter.js";
34
- import { DEFAULT_VARIANT_PRESETS } from "./mediaVariants.js";
35
-
36
- // =============================================================================
37
- // Create Variant (completed)
38
- // =============================================================================
39
-
40
- /**
41
- * Mutation to create a media variant that has already been processed.
42
- *
43
- * Use this when variant processing happens externally (e.g., in a serverless
44
- * function or image processing service) and you need to register the
45
- * completed variant in the CMS.
46
- *
47
- * @param assetId - Parent media asset ID
48
- * @param storageId - Storage ID of the variant file
49
- * @param variantType - Type of variant (thumbnail, responsive, format)
50
- * @param width - Width in pixels
51
- * @param height - Height in pixels
52
- * @param format - Output format (webp, avif, jpeg, etc.)
53
- * @param mimeType - MIME type of the variant
54
- * @param size - File size in bytes
55
- * @param quality - Quality setting used (0-100)
56
- * @param preset - Named preset if applicable
57
- * @param autoGenerated - Whether this was auto-generated
58
- * @param createdBy - User ID who created this variant
59
- * @returns The created variant document with URL
60
- *
61
- * @example
62
- * ```typescript
63
- * // After processing an image externally and uploading the result
64
- * const variant = await ctx.runMutation(api.mediaVariantMutations.createMediaVariant, {
65
- * assetId: assetId,
66
- * storageId: uploadedStorageId,
67
- * variantType: "responsive",
68
- * width: 480,
69
- * height: 320,
70
- * format: "webp",
71
- * mimeType: "image/webp",
72
- * size: 25600,
73
- * quality: 80,
74
- * preset: "small",
75
- * autoGenerated: true,
76
- * });
77
- * ```
78
- */
79
- export const createMediaVariant = mutation({
80
- args: createMediaVariantArgs.fields,
81
- returns: mediaVariantWithUrlDoc,
82
- handler: async (ctx, args) => {
83
- const {
84
- assetId,
85
- storageId,
86
- variantType,
87
- width,
88
- height,
89
- format,
90
- mimeType,
91
- size,
92
- quality,
93
- preset,
94
- autoGenerated = false,
95
- createdBy,
96
- } = args;
97
-
98
- // Validate parent asset exists and is not deleted
99
- const item = await ctx.db.get(assetId);
100
- if (!item || item.kind !== "asset") {
101
- throw new Error(`Media asset not found: ${assetId}`);
102
- }
103
- if (isDeleted(item)) {
104
- throw new Error(`Cannot create variant for deleted asset: ${assetId}`);
105
- }
106
- const asset = item;
107
-
108
- // Check for duplicate variant (same dimensions, format, and preset)
109
- const existingVariants = await ctx.db
110
- .query("mediaVariants")
111
- .withIndex("by_asset", (q) => q.eq("assetId", assetId))
112
- .filter((q) =>
113
- q.and(
114
- q.eq(q.field("variantType"), variantType),
115
- q.eq(q.field("width"), width),
116
- q.eq(q.field("height"), height),
117
- q.eq(q.field("format"), format),
118
- q.eq(q.field("deletedAt"), undefined),
119
- ),
120
- )
121
- .first();
122
-
123
- if (existingVariants) {
124
- throw new Error(
125
- `Variant already exists for asset ${assetId} with type=${variantType}, ` +
126
- `dimensions=${width}x${height}, format=${format}`,
127
- );
128
- }
129
-
130
- // Create the variant record
131
- const variantId = await ctx.db.insert("mediaVariants", {
132
- assetId,
133
- storageId,
134
- variantType,
135
- width,
136
- height,
137
- format,
138
- mimeType,
139
- size,
140
- quality,
141
- preset,
142
- autoGenerated,
143
- status: "completed",
144
- processingCompletedAt: Date.now(),
145
- createdBy,
146
- });
147
-
148
- // Get the created variant
149
- const variant = await ctx.db.get(variantId);
150
- if (!variant) {
151
- throw new Error("Failed to create variant");
152
- }
153
-
154
- // Resolve URL
155
- const url = await ctx.storage.getUrl(storageId);
156
-
157
- // Emit event
158
- await emitEvent(ctx, {
159
- eventType: "mediaAsset.updated",
160
- resourceType: "mediaAsset",
161
- resourceId: assetId.toString(),
162
- action: "updated",
163
- payload: {
164
- name: asset.name,
165
- mimeType: asset.mimeType,
166
- type: classifyMimeType(asset.mimeType),
167
- size: asset.size ?? 0,
168
- },
169
- userId: createdBy,
170
- metadata: {
171
- variantCreated: true,
172
- variantId: variantId.toString(),
173
- variantType,
174
- variantFormat: format,
175
- variantPreset: preset,
176
- },
177
- });
178
-
179
- return {
180
- ...variant,
181
- url,
182
- };
183
- },
184
- });
185
-
186
- // =============================================================================
187
- // Request Variant Generation
188
- // =============================================================================
189
-
190
- /**
191
- * Mutation to request async generation of a variant.
192
- *
193
- * Creates a variant record with "pending" status. An external processing
194
- * system should pick up pending variants, process them, upload the result,
195
- * and update the status to "completed".
196
- *
197
- * @param assetId - Parent media asset ID
198
- * @param variantType - Type of variant to generate
199
- * @param width - Target width (optional)
200
- * @param height - Target height (optional)
201
- * @param format - Output format
202
- * @param quality - Quality setting (0-100)
203
- * @param preset - Named preset to use
204
- * @param requestedBy - User ID requesting the variant
205
- * @returns The created variant document (pending status)
206
- *
207
- * @example
208
- * ```typescript
209
- * // Request a thumbnail variant be generated
210
- * const pendingVariant = await ctx.runMutation(api.mediaVariantMutations.requestVariantGeneration, {
211
- * assetId: assetId,
212
- * variantType: "thumbnail",
213
- * width: 150,
214
- * height: 150,
215
- * format: "webp",
216
- * quality: 80,
217
- * preset: "thumbnail",
218
- * });
219
- *
220
- * // The variant now has status: "pending"
221
- * // An external processor should pick it up and generate the image
222
- * ```
223
- */
224
- export const requestVariantGeneration = mutation({
225
- args: requestVariantGenerationArgs.fields,
226
- returns: mediaVariantDoc,
227
- handler: async (ctx, args) => {
228
- const {
229
- assetId,
230
- variantType,
231
- width,
232
- height,
233
- format,
234
- quality,
235
- preset,
236
- requestedBy,
237
- } = args;
238
-
239
- // Validate parent asset exists and is not deleted
240
- const item = await ctx.db.get(assetId);
241
- if (!item || item.kind !== "asset") {
242
- throw new Error(`Media asset not found: ${assetId}`);
243
- }
244
- if (isDeleted(item)) {
245
- throw new Error(`Cannot create variant for deleted asset: ${assetId}`);
246
- }
247
- const asset = item;
248
-
249
- // Validate asset is an image (variants only make sense for images)
250
- const assetType = classifyMimeType(asset.mimeType);
251
- if (assetType !== "image") {
252
- throw new Error(
253
- `Variants can only be generated for images. Asset type: ${assetType}`,
254
- );
255
- }
256
-
257
- // Check for existing pending/processing variant with same parameters
258
- const existingPending = await ctx.db
259
- .query("mediaVariants")
260
- .withIndex("by_asset", (q) => q.eq("assetId", assetId))
261
- .filter((q) =>
262
- q.and(
263
- q.eq(q.field("variantType"), variantType),
264
- q.eq(q.field("width"), width),
265
- q.eq(q.field("height"), height),
266
- q.eq(q.field("format"), format),
267
- q.or(
268
- q.eq(q.field("status"), "pending"),
269
- q.eq(q.field("status"), "processing"),
270
- ),
271
- ),
272
- )
273
- .first();
274
-
275
- if (existingPending) {
276
- // Return existing pending variant instead of creating duplicate
277
- return existingPending;
278
- }
279
-
280
- // Check for existing completed variant
281
- const existingCompleted = await ctx.db
282
- .query("mediaVariants")
283
- .withIndex("by_asset", (q) => q.eq("assetId", assetId))
284
- .filter((q) =>
285
- q.and(
286
- q.eq(q.field("variantType"), variantType),
287
- q.eq(q.field("width"), width),
288
- q.eq(q.field("height"), height),
289
- q.eq(q.field("format"), format),
290
- q.eq(q.field("status"), "completed"),
291
- q.eq(q.field("deletedAt"), undefined),
292
- ),
293
- )
294
- .first();
295
-
296
- if (existingCompleted) {
297
- // Variant already exists, return it
298
- return existingCompleted;
299
- }
300
-
301
- // Create placeholder storage ID (will be updated when processing completes)
302
- // For pending variants, we use a placeholder that will be replaced
303
- const variantId = await ctx.db.insert("mediaVariants", {
304
- assetId,
305
- storageId: asset.storageId, // Placeholder - will be replaced
306
- variantType,
307
- width,
308
- height,
309
- format,
310
- mimeType: getMimeTypeFromFormat(format),
311
- size: 0, // Unknown until processed
312
- quality,
313
- preset,
314
- autoGenerated: true,
315
- status: "pending",
316
- createdBy: requestedBy,
317
- });
318
-
319
- const variant = await ctx.db.get(variantId);
320
- if (!variant) {
321
- throw new Error("Failed to create pending variant");
322
- }
323
-
324
- return variant;
325
- },
326
- });
327
-
328
- // =============================================================================
329
- // Update Variant Status
330
- // =============================================================================
331
-
332
- /**
333
- * Mutation to update the status of a variant during processing.
334
- *
335
- * Used by the processing system to:
336
- * - Mark a variant as "processing" when it starts
337
- * - Mark as "completed" with final storage ID and dimensions
338
- * - Mark as "failed" with error message
339
- *
340
- * @param id - Variant ID to update
341
- * @param status - New status
342
- * @param storageId - Storage ID of completed variant
343
- * @param size - Final file size
344
- * @param mimeType - Final MIME type
345
- * @param width - Final width
346
- * @param height - Final height
347
- * @param errorMessage - Error message if failed
348
- * @returns Updated variant document
349
- *
350
- * @example
351
- * ```typescript
352
- * // Mark as processing
353
- * await ctx.runMutation(api.mediaVariantMutations.updateVariantStatus, {
354
- * id: variantId,
355
- * status: "processing",
356
- * });
357
- *
358
- * // ... process the image ...
359
- *
360
- * // Mark as completed
361
- * await ctx.runMutation(api.mediaVariantMutations.updateVariantStatus, {
362
- * id: variantId,
363
- * status: "completed",
364
- * storageId: processedStorageId,
365
- * size: 25600,
366
- * width: 480,
367
- * height: 320,
368
- * });
369
- * ```
370
- */
371
- export const updateVariantStatus = mutation({
372
- args: updateVariantStatusArgs.fields,
373
- returns: mediaVariantDoc,
374
- handler: async (ctx, args) => {
375
- const {
376
- id,
377
- status,
378
- storageId,
379
- size,
380
- mimeType,
381
- width,
382
- height,
383
- errorMessage,
384
- } = args;
385
-
386
- const variant = await ctx.db.get(id);
387
- if (!variant) {
388
- throw new Error(`Variant not found: ${id}`);
389
- }
390
-
391
- const updates: Record<string, unknown> = { status };
392
-
393
- if (status === "processing") {
394
- updates.processingStartedAt = Date.now();
395
- } else if (status === "completed") {
396
- updates.processingCompletedAt = Date.now();
397
- if (storageId) updates.storageId = storageId;
398
- if (size !== undefined) updates.size = size;
399
- if (mimeType) updates.mimeType = mimeType;
400
- if (width !== undefined) updates.width = width;
401
- if (height !== undefined) updates.height = height;
402
- } else if (status === "failed") {
403
- updates.processingCompletedAt = Date.now();
404
- if (errorMessage) updates.errorMessage = errorMessage;
405
- }
406
-
407
- await ctx.db.patch(id, updates);
408
-
409
- const updatedVariant = await ctx.db.get(id);
410
- if (!updatedVariant) {
411
- throw new Error("Failed to update variant");
412
- }
413
-
414
- return updatedVariant;
415
- },
416
- });
417
-
418
- // =============================================================================
419
- // Delete Variant
420
- // =============================================================================
421
-
422
- /**
423
- * Mutation to delete a media variant.
424
- *
425
- * Supports soft delete (default) and hard delete.
426
- * Hard delete also removes the storage file.
427
- *
428
- * @param id - Variant ID to delete
429
- * @param hardDelete - If true, permanently delete and remove storage file
430
- * @param deletedBy - User ID performing the deletion
431
- * @returns Deleted variant document
432
- *
433
- * @example
434
- * ```typescript
435
- * // Soft delete (can be recovered)
436
- * await ctx.runMutation(api.mediaVariantMutations.deleteMediaVariant, {
437
- * id: variantId,
438
- * });
439
- *
440
- * // Hard delete (permanent)
441
- * await ctx.runMutation(api.mediaVariantMutations.deleteMediaVariant, {
442
- * id: variantId,
443
- * hardDelete: true,
444
- * });
445
- * ```
446
- */
447
- export const deleteMediaVariant = mutation({
448
- args: deleteMediaVariantArgs.fields,
449
- returns: mediaVariantDoc,
450
- handler: async (ctx, args) => {
451
- const { id, hardDelete = false } = args;
452
-
453
- const variant = await ctx.db.get(id);
454
- if (!variant) {
455
- throw new Error(`Variant not found: ${id}`);
456
- }
457
-
458
- if (hardDelete) {
459
- // Delete storage file if it's not a placeholder
460
- if (variant.status === "completed") {
461
- try {
462
- await ctx.storage.delete(variant.storageId);
463
- } catch {
464
- // Storage file might already be deleted
465
- }
466
- }
467
-
468
- // Hard delete the variant record
469
- await ctx.db.delete(id);
470
- } else {
471
- // Soft delete
472
- await ctx.db.patch(id, { deletedAt: Date.now() });
473
- }
474
-
475
- return variant;
476
- },
477
- });
478
-
479
- // =============================================================================
480
- // Delete All Variants for Asset
481
- // =============================================================================
482
-
483
- /**
484
- * Mutation to delete all variants for a media asset.
485
- *
486
- * Useful when deleting the parent asset or when regenerating
487
- * all variants from scratch.
488
- *
489
- * @param assetId - Parent asset ID
490
- * @param hardDelete - If true, permanently delete and remove storage files
491
- * @param deletedBy - User ID performing the deletion
492
- * @returns Summary of deleted variants
493
- *
494
- * @example
495
- * ```typescript
496
- * // Soft delete all variants
497
- * const result = await ctx.runMutation(api.mediaVariantMutations.deleteAssetVariants, {
498
- * assetId: assetId,
499
- * });
500
- * console.log(`Deleted ${result.deleted} variants`);
501
- *
502
- * // Hard delete all variants
503
- * await ctx.runMutation(api.mediaVariantMutations.deleteAssetVariants, {
504
- * assetId: assetId,
505
- * hardDelete: true,
506
- * });
507
- * ```
508
- */
509
- export const deleteAssetVariants = mutation({
510
- args: deleteAssetVariantsArgs.fields,
511
- returns: v.object({
512
- deleted: v.number(),
513
- assetId: v.id("mediaItems"),
514
- }),
515
- handler: async (ctx, args) => {
516
- const { assetId, hardDelete = false } = args;
517
-
518
- const variants = await ctx.db
519
- .query("mediaVariants")
520
- .withIndex("by_asset", (q) => q.eq("assetId", assetId))
521
- .filter((q) => q.eq(q.field("deletedAt"), undefined))
522
- .collect();
523
-
524
- let deletedCount = 0;
525
-
526
- for (const variant of variants) {
527
- if (hardDelete) {
528
- // Delete storage file
529
- if (variant.status === "completed") {
530
- try {
531
- await ctx.storage.delete(variant.storageId);
532
- } catch {
533
- // Storage file might already be deleted
534
- }
535
- }
536
- await ctx.db.delete(variant._id);
537
- } else {
538
- await ctx.db.patch(variant._id, { deletedAt: Date.now() });
539
- }
540
- deletedCount++;
541
- }
542
-
543
- return {
544
- deleted: deletedCount,
545
- assetId,
546
- };
547
- },
548
- });
549
-
550
- // =============================================================================
551
- // Generate Variants from Presets
552
- // =============================================================================
553
-
554
- /**
555
- * Mutation to request generation of multiple variants from presets.
556
- *
557
- * Creates pending variant records for each preset. An external processor
558
- * should pick up and process these variants.
559
- *
560
- * @param assetId - Parent asset ID
561
- * @param presets - Array of preset names to generate
562
- * @param requestedBy - User ID requesting the variants
563
- * @returns Summary of created variant requests
564
- *
565
- * @example
566
- * ```typescript
567
- * // Generate thumbnail and responsive sizes
568
- * const result = await ctx.runMutation(api.mediaVariantMutations.generateFromPresets, {
569
- * assetId: assetId,
570
- * presets: ["thumbnail", "small", "medium", "large"],
571
- * });
572
- *
573
- * console.log(`Queued ${result.succeeded} variants for processing`);
574
- * ```
575
- */
576
- export const generateFromPresets = mutation({
577
- args: {
578
- assetId: v.id("mediaItems"),
579
- presets: v.array(v.string()),
580
- requestedBy: v.optional(v.string()),
581
- },
582
- returns: generateVariantsResult,
583
- handler: async (ctx, args) => {
584
- const { assetId, presets, requestedBy } = args;
585
-
586
- // Validate asset exists and is an image
587
- const item = await ctx.db.get(assetId);
588
- if (!item || item.kind !== "asset") {
589
- throw new Error(`Media asset not found: ${assetId}`);
590
- }
591
- if (isDeleted(item)) {
592
- throw new Error(`Cannot generate variants for deleted asset: ${assetId}`);
593
- }
594
- const asset = item;
595
- const assetMediaType = classifyMimeType(asset.mimeType);
596
- if (assetMediaType !== "image") {
597
- throw new Error(
598
- `Variants can only be generated for images. Asset type: ${assetMediaType}`,
599
- );
600
- }
601
-
602
- const results: {
603
- preset: string;
604
- success: boolean;
605
- variantId?: Id<"mediaVariants">;
606
- error?: string;
607
- }[] = [];
608
-
609
- for (const presetName of presets) {
610
- const preset =
611
- DEFAULT_VARIANT_PRESETS[
612
- presetName as keyof typeof DEFAULT_VARIANT_PRESETS
613
- ];
614
-
615
- if (!preset) {
616
- results.push({
617
- preset: presetName,
618
- success: false,
619
- error: `Unknown preset: ${presetName}`,
620
- });
621
- continue;
622
- }
623
-
624
- try {
625
- // Check for existing variant
626
- const existing = await ctx.db
627
- .query("mediaVariants")
628
- .withIndex("by_asset_and_preset", (q) =>
629
- q.eq("assetId", assetId).eq("preset", presetName),
630
- )
631
- .filter((q) =>
632
- q.and(
633
- q.eq(q.field("deletedAt"), undefined),
634
- q.or(
635
- q.eq(q.field("status"), "pending"),
636
- q.eq(q.field("status"), "processing"),
637
- q.eq(q.field("status"), "completed"),
638
- ),
639
- ),
640
- )
641
- .first();
642
-
643
- if (existing) {
644
- results.push({
645
- preset: presetName,
646
- success: true,
647
- variantId: existing._id,
648
- });
649
- continue;
650
- }
651
-
652
- // Create pending variant
653
- const variantId = await ctx.db.insert("mediaVariants", {
654
- assetId,
655
- storageId: asset.storageId, // Placeholder
656
- variantType: preset.variantType,
657
- width: "width" in preset ? preset.width : undefined,
658
- height: "height" in preset ? preset.height : undefined,
659
- format: preset.format,
660
- mimeType: getMimeTypeFromFormat(preset.format),
661
- size: 0,
662
- quality: preset.quality,
663
- preset: presetName,
664
- autoGenerated: true,
665
- status: "pending",
666
- createdBy: requestedBy,
667
- });
668
-
669
- results.push({
670
- preset: presetName,
671
- success: true,
672
- variantId,
673
- });
674
- } catch (error) {
675
- results.push({
676
- preset: presetName,
677
- success: false,
678
- error: error instanceof Error ? error.message : String(error),
679
- });
680
- }
681
- }
682
-
683
- const succeeded = results.filter((r) => r.success).length;
684
- const failed = results.filter((r) => !r.success).length;
685
-
686
- // Emit event for bulk variant generation request
687
- await emitEvent(ctx, {
688
- eventType: "mediaAsset.updated",
689
- resourceType: "mediaAsset",
690
- resourceId: assetId.toString(),
691
- action: "updated",
692
- payload: {
693
- name: asset.name,
694
- mimeType: asset.mimeType,
695
- type: classifyMimeType(asset.mimeType),
696
- size: asset.size ?? 0,
697
- },
698
- userId: requestedBy,
699
- metadata: {
700
- variantsRequested: true,
701
- presetsRequested: presets,
702
- succeeded,
703
- failed,
704
- },
705
- });
706
-
707
- return {
708
- total: presets.length,
709
- succeeded,
710
- failed,
711
- results: results.map((r) => ({
712
- ...r,
713
- variantId: r.variantId ? r.variantId : undefined,
714
- })),
715
- };
716
- },
717
- });
718
-
719
- // =============================================================================
720
- // Restore Variant (undo soft delete)
721
- // =============================================================================
722
-
723
- /**
724
- * Mutation to restore a soft-deleted variant.
725
- *
726
- * @param id - Variant ID to restore
727
- * @param restoredBy - User ID performing the restoration
728
- * @returns Restored variant document
729
- *
730
- * @example
731
- * ```typescript
732
- * await ctx.runMutation(api.mediaVariantMutations.restoreMediaVariant, {
733
- * id: variantId,
734
- * restoredBy: userId,
735
- * });
736
- * ```
737
- */
738
- export const restoreMediaVariant = mutation({
739
- args: {
740
- id: v.id("mediaVariants"),
741
- restoredBy: v.optional(v.string()),
742
- },
743
- returns: mediaVariantDoc,
744
- handler: async (ctx, args) => {
745
- const { id } = args;
746
-
747
- const variant = await ctx.db.get(id);
748
- if (!variant) {
749
- throw new Error(`Variant not found: ${id}`);
750
- }
751
-
752
- if (!isDeleted(variant)) {
753
- // Already restored, return as-is
754
- return variant;
755
- }
756
-
757
- // Verify parent asset is not deleted
758
- const asset = await ctx.db.get(variant.assetId);
759
- if (!asset) {
760
- throw new Error(
761
- `Cannot restore variant: parent asset not found: ${variant.assetId}`,
762
- );
763
- }
764
- if (isDeleted(asset)) {
765
- throw new Error(
766
- `Cannot restore variant: parent asset is deleted: ${variant.assetId}`,
767
- );
768
- }
769
-
770
- await ctx.db.patch(id, { deletedAt: undefined });
771
-
772
- const restoredVariant = await ctx.db.get(id);
773
- if (!restoredVariant) {
774
- throw new Error("Failed to restore variant");
775
- }
776
-
777
- return restoredVariant;
778
- },
779
- });
780
-
781
- // =============================================================================
782
- // Internal: Process Pending Variant
783
- // =============================================================================
784
-
785
- /**
786
- * Internal mutation for the processing system to mark a variant as processing.
787
- *
788
- * This is called when a processor picks up a pending variant from the queue.
789
- */
790
- export const markVariantProcessing = internalMutation({
791
- args: {
792
- id: v.id("mediaVariants"),
793
- },
794
- returns: v.union(mediaVariantDoc, v.null()),
795
- handler: async (ctx, args) => {
796
- const { id } = args;
797
-
798
- const variant = await ctx.db.get(id);
799
- if (!variant || variant.status !== "pending") {
800
- return null;
801
- }
802
-
803
- await ctx.db.patch(id, {
804
- status: "processing",
805
- processingStartedAt: Date.now(),
806
- });
807
-
808
- return await ctx.db.get(id);
809
- },
810
- });
811
-
812
- /**
813
- * Internal mutation to complete a variant after processing.
814
- */
815
- export const completeVariant = internalMutation({
816
- args: {
817
- id: v.id("mediaVariants"),
818
- storageId: v.id("_storage"),
819
- size: v.number(),
820
- width: v.optional(v.number()),
821
- height: v.optional(v.number()),
822
- },
823
- returns: mediaVariantDoc,
824
- handler: async (ctx, args) => {
825
- const { id, storageId, size, width, height } = args;
826
-
827
- const variant = await ctx.db.get(id);
828
- if (!variant) {
829
- throw new Error(`Variant not found: ${id}`);
830
- }
831
-
832
- await ctx.db.patch(id, {
833
- status: "completed",
834
- storageId,
835
- size,
836
- width: width ?? variant.width,
837
- height: height ?? variant.height,
838
- processingCompletedAt: Date.now(),
839
- });
840
-
841
- const completed = await ctx.db.get(id);
842
- if (!completed) {
843
- throw new Error("Failed to complete variant");
844
- }
845
-
846
- return completed;
847
- },
848
- });
849
-
850
- /**
851
- * Internal mutation to mark a variant as failed.
852
- */
853
- export const failVariant = internalMutation({
854
- args: {
855
- id: v.id("mediaVariants"),
856
- errorMessage: v.string(),
857
- },
858
- returns: mediaVariantDoc,
859
- handler: async (ctx, args) => {
860
- const { id, errorMessage } = args;
861
-
862
- const variant = await ctx.db.get(id);
863
- if (!variant) {
864
- throw new Error(`Variant not found: ${id}`);
865
- }
866
-
867
- await ctx.db.patch(id, {
868
- status: "failed",
869
- errorMessage,
870
- processingCompletedAt: Date.now(),
871
- });
872
-
873
- const failed = await ctx.db.get(id);
874
- if (!failed) {
875
- throw new Error("Failed to update variant");
876
- }
877
-
878
- return failed;
879
- },
880
- });
881
-
882
- // =============================================================================
883
- // Helper Functions
884
- // =============================================================================
885
-
886
- /**
887
- * Get MIME type from format string.
888
- */
889
- function getMimeTypeFromFormat(format: string): string {
890
- const mimeTypes: Record<string, string> = {
891
- jpeg: "image/jpeg",
892
- jpg: "image/jpeg",
893
- png: "image/png",
894
- webp: "image/webp",
895
- avif: "image/avif",
896
- gif: "image/gif",
897
- svg: "image/svg+xml",
898
- };
899
- return mimeTypes[format.toLowerCase()] || `image/${format}`;
900
- }