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