convex-cms 0.0.1 → 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 (267) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +99 -0
  3. package/admin-dist/nitro.json +15 -0
  4. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  5. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  6. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  7. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  8. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  9. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  10. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  11. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  12. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  13. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  14. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  15. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  16. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  17. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  18. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  19. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  20. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  21. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  22. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  23. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  24. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  25. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  26. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  27. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  28. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  29. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  30. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  31. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  32. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  33. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  34. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  35. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  36. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  37. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  38. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  39. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  40. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  41. package/admin-dist/public/favicon.ico +0 -0
  42. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  45. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  46. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  83. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  84. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  88. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  89. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  90. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  91. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  92. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  93. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  94. package/admin-dist/server/_libs/clsx.mjs +16 -0
  95. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  96. package/admin-dist/server/_libs/convex.mjs +4841 -0
  97. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  98. package/admin-dist/server/_libs/croner.mjs +1 -0
  99. package/admin-dist/server/_libs/crossws.mjs +1 -0
  100. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  101. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  102. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  103. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  104. package/admin-dist/server/_libs/h3.mjs +401 -0
  105. package/admin-dist/server/_libs/hookable.mjs +1 -0
  106. package/admin-dist/server/_libs/isbot.mjs +20 -0
  107. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  108. package/admin-dist/server/_libs/ohash.mjs +1 -0
  109. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  110. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  111. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  112. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  113. package/admin-dist/server/_libs/rou3.mjs +8 -0
  114. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  115. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  116. package/admin-dist/server/_libs/srvx.mjs +719 -0
  117. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  118. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  119. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  120. package/admin-dist/server/_libs/tslib.mjs +39 -0
  121. package/admin-dist/server/_libs/ufo.mjs +54 -0
  122. package/admin-dist/server/_libs/unctx.mjs +1 -0
  123. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  124. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  125. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  126. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  127. package/admin-dist/server/_libs/zod.mjs +4223 -0
  128. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  129. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  130. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  131. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  132. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  133. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  134. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  135. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  136. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  137. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  138. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  139. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  140. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  141. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  142. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  143. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  144. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  145. package/admin-dist/server/_ssr/index.mjs +1275 -0
  146. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  147. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  148. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  149. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  150. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  151. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  152. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  153. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  154. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  155. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  156. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  157. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  158. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  159. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  160. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  161. package/admin-dist/server/favicon.ico +0 -0
  162. package/admin-dist/server/index.mjs +627 -0
  163. package/dist/cli/index.js +0 -0
  164. package/dist/client/admin-config.d.ts +0 -1
  165. package/dist/client/admin-config.d.ts.map +1 -1
  166. package/dist/client/admin-config.js +0 -1
  167. package/dist/client/admin-config.js.map +1 -1
  168. package/dist/client/adminApi.d.ts.map +1 -1
  169. package/dist/client/agentTools.d.ts +1237 -135
  170. package/dist/client/agentTools.d.ts.map +1 -1
  171. package/dist/client/agentTools.js +33 -9
  172. package/dist/client/agentTools.js.map +1 -1
  173. package/dist/client/index.d.ts +1 -1
  174. package/dist/client/index.d.ts.map +1 -1
  175. package/dist/client/index.js.map +1 -1
  176. package/dist/component/_generated/component.d.ts +9 -0
  177. package/dist/component/_generated/component.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.d.ts +35 -0
  179. package/dist/component/mediaAssets.d.ts.map +1 -1
  180. package/dist/component/mediaAssets.js +81 -0
  181. package/dist/component/mediaAssets.js.map +1 -1
  182. package/dist/test.d.ts.map +1 -1
  183. package/dist/test.js +2 -1
  184. package/dist/test.js.map +1 -1
  185. package/package.json +24 -9
  186. package/dist/component/auditLog.d.ts +0 -410
  187. package/dist/component/auditLog.d.ts.map +0 -1
  188. package/dist/component/auditLog.js +0 -607
  189. package/dist/component/auditLog.js.map +0 -1
  190. package/dist/component/types.d.ts +0 -4
  191. package/dist/component/types.d.ts.map +0 -1
  192. package/dist/component/types.js +0 -2
  193. package/dist/component/types.js.map +0 -1
  194. package/src/cli/commands/admin.ts +0 -104
  195. package/src/cli/index.ts +0 -21
  196. package/src/cli/utils/detectConvexUrl.ts +0 -54
  197. package/src/cli/utils/openBrowser.ts +0 -16
  198. package/src/client/admin-config.ts +0 -138
  199. package/src/client/adminApi.ts +0 -942
  200. package/src/client/agentTools.ts +0 -1311
  201. package/src/client/argTypes.ts +0 -316
  202. package/src/client/field-types.ts +0 -187
  203. package/src/client/index.ts +0 -1301
  204. package/src/client/queryBuilder.ts +0 -1100
  205. package/src/client/schema/codegen.ts +0 -500
  206. package/src/client/schema/defineContentType.ts +0 -501
  207. package/src/client/schema/index.ts +0 -169
  208. package/src/client/schema/schemaDrift.ts +0 -574
  209. package/src/client/schema/typedClient.ts +0 -688
  210. package/src/client/schema/types.ts +0 -666
  211. package/src/client/types.ts +0 -723
  212. package/src/client/workflows.ts +0 -141
  213. package/src/client/wrapper.ts +0 -4304
  214. package/src/component/_generated/api.ts +0 -140
  215. package/src/component/_generated/component.ts +0 -5029
  216. package/src/component/_generated/dataModel.ts +0 -60
  217. package/src/component/_generated/server.ts +0 -156
  218. package/src/component/authorization.ts +0 -647
  219. package/src/component/authorizationHooks.ts +0 -668
  220. package/src/component/bulkOperations.ts +0 -687
  221. package/src/component/contentEntries.ts +0 -1976
  222. package/src/component/contentEntryMutations.ts +0 -1223
  223. package/src/component/contentEntryValidation.ts +0 -707
  224. package/src/component/contentLock.ts +0 -550
  225. package/src/component/contentTypeMigration.ts +0 -1064
  226. package/src/component/contentTypeMutations.ts +0 -969
  227. package/src/component/contentTypes.ts +0 -346
  228. package/src/component/convex.config.ts +0 -44
  229. package/src/component/documentTypes.ts +0 -240
  230. package/src/component/eventEmitter.ts +0 -485
  231. package/src/component/exportImport.ts +0 -1169
  232. package/src/component/index.ts +0 -491
  233. package/src/component/lib/deepReferenceResolver.ts +0 -999
  234. package/src/component/lib/errors.ts +0 -816
  235. package/src/component/lib/index.ts +0 -145
  236. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  237. package/src/component/lib/metadataExtractor.ts +0 -792
  238. package/src/component/lib/mutationAuth.ts +0 -199
  239. package/src/component/lib/queries.ts +0 -79
  240. package/src/component/lib/ragContentChunker.ts +0 -1371
  241. package/src/component/lib/referenceResolver.ts +0 -430
  242. package/src/component/lib/slugGenerator.ts +0 -262
  243. package/src/component/lib/slugUniqueness.ts +0 -333
  244. package/src/component/lib/softDelete.ts +0 -44
  245. package/src/component/localeFallbackChain.ts +0 -673
  246. package/src/component/localeFields.ts +0 -896
  247. package/src/component/mediaAssetMutations.ts +0 -725
  248. package/src/component/mediaAssets.ts +0 -932
  249. package/src/component/mediaFolderMutations.ts +0 -1046
  250. package/src/component/mediaUploadMutations.ts +0 -224
  251. package/src/component/mediaVariantMutations.ts +0 -900
  252. package/src/component/mediaVariants.ts +0 -793
  253. package/src/component/ragContentIndexer.ts +0 -1067
  254. package/src/component/rateLimitHooks.ts +0 -572
  255. package/src/component/roles.ts +0 -1360
  256. package/src/component/scheduledPublish.ts +0 -358
  257. package/src/component/schema.ts +0 -617
  258. package/src/component/taxonomies.ts +0 -949
  259. package/src/component/taxonomyMutations.ts +0 -1210
  260. package/src/component/trash.ts +0 -724
  261. package/src/component/userContext.ts +0 -898
  262. package/src/component/validation.ts +0 -1388
  263. package/src/component/validators.ts +0 -949
  264. package/src/component/versionMutations.ts +0 -392
  265. package/src/component/webhookTrigger.ts +0 -1922
  266. package/src/react/index.ts +0 -898
  267. 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
- }