feat(core-flows,product,types): scoped variant images (#13623)

* wip(product): variant images

* fix: return type

* wip: repo and list approach

* fix: redo repo method, make test pass

* fix: change getVariantImages impl

* feat: update test

* feat: API and core flows layer

* wip: integration spec

* fix: deterministic test

* chore: refactor and simplify, cleanup, remove repo method

* wip: batch add all images to all vairants

* fix: remove, expand testing

* refactor: pass variants instead of refetch

* chore: expand integration test

* feat: test multi assign route

* fix: remove `/admin/products/:id/variants/images` route

* feat: batch images to variant endpoint

* fix: length assertion

* feat: variant thumbnail

* fix: send variant thumbnail by default

* fix: product export test assertion

* fix: test

* feat: variant thumbnail on line item

* fix: add missing list and count method, update types

* feat: optimise variant images lookups

* feat: thumbnail management in core flows

* fix: typos, type, build

* feat: cascade delete to pivot table, rm unused unused fields

* feat(dashboard): variant images management UI (#13670)

* wip(dashboard): setup variant media form

* wip: cleanup table and images, wip check handler

* feat: proper sidebar functionallity

* fefat: add js-sdk and hooks

* feat: allow only one selection

* wip: lazy load variants in the table

* feat: new variants management for images on product details

* chore: refactor

* wip: variant details page work

* fix: cleanup media section, fix issues and types

* feat: correct scoped images, cleanup in edit modal

* feat: js sdk and hooks, filter out product images on variant details, labels, add API call and wrap UI

* chore: cleanup

* refacto: rename route

* feat: thumbnail functionallity

* fix: refresh checked after revalidation load

* fix: rm unused, refactor type

* Create thirty-clocks-refuse.md

* feat: new add remove variant media layout

* feat: new image add UX

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>

* fix: table name in migration

* chore: update changesets

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Frane Polić
2025-10-26 15:15:40 +01:00
committed by GitHub
co-authored by Oli Juhl
parent bafd006094
commit 4757281677
57 changed files with 3323 additions and 80 deletions
@@ -155,6 +155,7 @@ export const productVariantsFields = [
"is_discountable",
"variant_option_values",
"barcode",
"thumbnail",
"product.id",
"product.title",
"product.description",
@@ -57,6 +57,7 @@ type AddItemProductDTO = ProductDTO & {
}
export interface PrepareVariantLineItemInput extends ProductVariantDTO {
thumbnail: string
inventory_items: { inventory: InventoryItemDTO }[]
calculated_price: {
calculated_price: {
@@ -140,7 +141,8 @@ export function prepareLineItemData(data: PrepareLineItemDataInput) {
quantity: item?.quantity,
title: variant?.product?.title ?? item?.title,
subtitle: variant?.title ?? item?.subtitle,
thumbnail: variant?.product?.thumbnail ?? item?.thumbnail,
thumbnail:
variant?.thumbnail ?? variant?.product?.thumbnail ?? item?.thumbnail,
product_id: variant?.product?.id ?? item?.product_id,
product_title: variant?.product?.title ?? item?.product_title,
@@ -0,0 +1,58 @@
import type { IProductModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
export const addImageToVariantsStepId = "add-image-to-variants"
/**
* This step adds an image to one or more product variants.
*
* @example
* const data = addImageToVariantsStep({
* image_id: "img_123",
* add: ["variant_123", "variant_456"]
* })
*/
export const addImageToVariantsStep = createStep(
addImageToVariantsStepId,
async (input: { image_id: string; add: string[] }, { container }) => {
if (!input.add.length) {
return new StepResponse([], { added: [], image_id: input.image_id })
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = input.add.map((variant_id) => ({
image_id: input.image_id,
variant_id,
}))
await productModuleService.addImageToVariant(data)
return new StepResponse(input.add, {
added: input.add,
image_id: input.image_id,
})
},
async (
compensationData: { added: string[]; image_id: string } | undefined,
{ container }
) => {
if (!compensationData?.added?.length || !compensationData?.image_id) {
return
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = compensationData.added.map((variant_id) => ({
image_id: compensationData.image_id,
variant_id,
}))
await productModuleService.removeImageFromVariant(data)
}
)
@@ -0,0 +1,58 @@
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { IProductModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
export const addImagesToVariantStepId = "add-images-to-variant"
/**
* This step adds one or more images to a product variant.
*
* @example
* const data = addImagesToVariantStep({
* variant_id: "variant_123",
* add: ["img_123", "img_456"]
* })
*/
export const addImagesToVariantStep = createStep(
addImagesToVariantStepId,
async (input: { variant_id: string; add: string[] }, { container }) => {
if (!input.add.length) {
return new StepResponse([], { added: [], variant_id: input.variant_id })
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = input.add.map((image_id) => ({
image_id,
variant_id: input.variant_id,
}))
await productModuleService.addImageToVariant(data)
return new StepResponse(input.add, {
added: input.add,
variant_id: input.variant_id,
})
},
async (
compensationData: { added: string[]; variant_id: string } | undefined,
{ container }
) => {
if (!compensationData?.added?.length || !compensationData?.variant_id) {
return
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = compensationData.added.map((image_id) => ({
image_id,
variant_id: compensationData.variant_id,
}))
await productModuleService.removeImageFromVariant(data)
}
)
@@ -1,3 +1,6 @@
export * from "./add-image-to-variants"
export * from "./add-images-to-variant"
export * from "./remove-images-from-variant"
export * from "./create-products"
export * from "./update-products"
export * from "./delete-products"
@@ -21,6 +24,7 @@ export * from "./delete-product-types"
export * from "./create-product-tags"
export * from "./update-product-tags"
export * from "./delete-product-tags"
export * from "./remove-image-from-variants"
export * from "./generate-product-csv"
export * from "./parse-product-csv"
export * from "./wait-confirmation-product-import"
@@ -0,0 +1,58 @@
import type { IProductModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
export const removeImageFromVariantsStepId = "remove-image-from-variants"
/**
* This step removes an image from one or more product variants.
*
* @example
* const data = removeImageFromVariantsStep({
* image_id: "img_123",
* remove: ["variant_123", "variant_456"]
* })
*/
export const removeImageFromVariantsStep = createStep(
removeImageFromVariantsStepId,
async (input: { image_id: string; remove: string[] }, { container }) => {
if (!input.remove.length) {
return new StepResponse([], { removed: [], image_id: input.image_id })
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = input.remove.map((variant_id) => ({
image_id: input.image_id,
variant_id,
}))
await productModuleService.removeImageFromVariant(data)
return new StepResponse(input.remove, {
removed: input.remove,
image_id: input.image_id,
})
},
async (
compensationData: { removed: string[]; image_id: string } | undefined,
{ container }
) => {
if (!compensationData?.removed?.length || !compensationData?.image_id) {
return
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = compensationData.removed.map((variant_id) => ({
image_id: compensationData.image_id,
variant_id,
}))
await productModuleService.addImageToVariant(data)
}
)
@@ -0,0 +1,58 @@
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
import { IProductModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
export const removeImagesFromVariantStepId = "remove-images-from-variant"
/**
* This step removes one or more images from a product variant.
*
* @example
* const data = removeImagesFromVariantStep({
* variant_id: "variant_123",
* remove: ["img_123", "img_456"]
* })
*/
export const removeImagesFromVariantStep = createStep(
removeImagesFromVariantStepId,
async (input: { variant_id: string; remove: string[] }, { container }) => {
if (!input.remove.length) {
return new StepResponse([], { removed: [], variant_id: input.variant_id })
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = input.remove.map((image_id) => ({
image_id,
variant_id: input.variant_id,
}))
await productModuleService.removeImageFromVariant(data)
return new StepResponse(input.remove, {
removed: input.remove,
variant_id: input.variant_id,
})
},
async (
compensationData: { removed: string[]; variant_id: string } | undefined,
{ container }
) => {
if (!compensationData?.removed?.length || !compensationData?.variant_id) {
return
}
const productModuleService = container.resolve<IProductModuleService>(
Modules.PRODUCT
)
const data = compensationData.removed.map((image_id) => ({
image_id,
variant_id: compensationData.variant_id,
}))
await productModuleService.addImageToVariant(data)
}
)
@@ -0,0 +1,169 @@
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
parallelize,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { ProductTypes } from "@medusajs/framework/types"
import {
addImageToVariantsStep,
removeImageFromVariantsStep,
updateProductVariantsStep,
} from "../steps"
import { useQueryGraphStep } from "../../common"
/**
* The input for the batch image-variant workflow.
*/
export interface BatchImageVariantsWorkflowInput {
/**
* The ID of the image to manage variants for.
*/
image_id: string
/**
* The variant IDs to add to the image.
*/
add?: string[]
/**
* The variant IDs to remove from the image.
*/
remove?: string[]
}
/**
* The result of the batch image-variant workflow.
*/
export interface BatchImageVariantsWorkflowOutput {
/**
* The variant IDs that were added to the image.
*/
added: string[]
/**
* The variant IDs that were removed from the image.
*/
removed: string[]
}
export const batchImageVariantsWorkflowId = "batch-image-variants"
/**
* This workflow manages the association between product images and variants in bulk.
* It's used by the [Batch Image Variants Admin API Route](https://docs.medusajs.com/api/admin#products_postproductsidimagesimage_idvariantsbatch).
*
* You can use this workflow within your own customizations or custom workflows to manage image-variant associations in bulk.
* This is also useful when writing a [seed script](https://docs.medusajs.com/learn/fundamentals/custom-cli-scripts/seed-data) or a custom import script.
*
* @example
* const { result } = await batchImageVariantsWorkflow(container)
* .run({
* input: {
* image_id: "img_123",
* add: ["variant_123", "variant_456"],
* remove: ["variant_789"]
* }
* })
*
* @summary
*
* Manage image-variant associations in bulk.
*/
export const batchImageVariantsWorkflow = createWorkflow(
batchImageVariantsWorkflowId,
(
input: WorkflowData<BatchImageVariantsWorkflowInput>
): WorkflowResponse<BatchImageVariantsWorkflowOutput> => {
const normalizedInput = transform({ input }, (data) => {
return {
image_id: data.input.image_id,
add: data.input.add ?? [],
remove: data.input.remove ?? [],
}
})
const res = parallelize(
addImageToVariantsStep(normalizedInput),
removeImageFromVariantsStep(normalizedInput)
)
const updateData = when(
"should-remove-variants",
{ normalizedInput },
(data) => data.normalizedInput.remove.length > 0
).then(() => {
const imageId = transform({ normalizedInput }, (data) => {
return data.normalizedInput.image_id
})
const variantsQuery = useQueryGraphStep({
entity: "variants",
fields: ["id", "thumbnail"],
filters: {
id: normalizedInput.remove,
},
}).config({ name: "get-variants-for-thumbnail-check" })
const { data: image } = useQueryGraphStep({
entity: "product_image",
fields: ["id", "url"],
filters: {
id: imageId,
},
options: {
isList: false,
},
}).config({ name: "get-image-for-thumbnail-check" })
const updateData = transform(
{
variants: variantsQuery.data,
image: image,
},
(data) => {
const imageUrl =
typeof data.image?.url === "string" ? data.image.url : null
if (!imageUrl) {
return null
}
return {
selector: {
id: normalizedInput.remove,
thumbnail: imageUrl,
},
update: {
thumbnail: null,
},
}
}
)
return updateData
})
when(
"should-update-variants",
{ updateData },
(data) =>
data.updateData !== null && typeof data.updateData !== "undefined"
).then(() => {
updateProductVariantsStep(
updateData! as {
selector: ProductTypes.FilterableProductVariantProps
update: ProductTypes.UpdateProductVariantDTO
}
)
})
const response = transform({ res, input }, (data) => {
return {
added: data.res[0] ?? [],
removed: data.res[1] ?? [],
}
})
return new WorkflowResponse(response)
}
)
@@ -0,0 +1,150 @@
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
parallelize,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { ProductVariantDTO } from "@medusajs/types"
import {
addImagesToVariantStep,
removeImagesFromVariantStep,
updateProductVariantsStep,
} from "../steps"
import { useQueryGraphStep } from "../../common"
/**
* The input for the batch variant-images workflow.
*/
export interface BatchVariantImagesWorkflowInput {
/**
* The ID of the variant to manage images for.
*/
variant_id: string
/**
* The image IDs to add to the variant.
*/
add?: string[]
/**
* The image IDs to remove from the variant.
*/
remove?: string[]
}
/**
* The result of the batch variant-images workflow.
*/
export interface BatchVariantImagesWorkflowOutput {
/**
* The image IDs that were added to the variant.
*/
added: string[]
/**
* The image IDs that were removed from the variant.
*/
removed: string[]
}
export const batchVariantImagesWorkflowId = "batch-variant-images"
/**
* This workflow manages the association between product variants and images in bulk.
* It's used by the [Batch Variant Images Admin API Route](https://docs.medusajs.com/api/admin#products_postproductsidvariantsvariant_idimagesbatch).
*
* You can use this workflow within your own customizations or custom workflows to manage variant-image associations in bulk.
* This is also useful when writing a [seed script](https://docs.medusajs.com/learn/fundamentals/custom-cli-scripts/seed-data) or a custom import script.
*
* @example
* const { result } = await batchVariantImagesWorkflow(container)
* .run({
* input: {
* variant_id: "variant_123",
* add: ["img_123", "img_456"],
* remove: ["img_789"]
* }
* })
*
* @summary
*
* Manage variant-image associations in bulk.
*/
export const batchVariantImagesWorkflow = createWorkflow(
batchVariantImagesWorkflowId,
(
input: WorkflowData<BatchVariantImagesWorkflowInput>
): WorkflowResponse<BatchVariantImagesWorkflowOutput> => {
const normalizedInput = transform({ input }, (data) => {
return {
variant_id: data.input.variant_id,
add: data.input.add ?? [],
remove: data.input.remove ?? [],
}
})
const res = parallelize(
addImagesToVariantStep(normalizedInput),
removeImagesFromVariantStep(normalizedInput)
)
const shouldUpdateVariantThumbnail = when(
"images-removed",
{ normalizedInput },
(data) => data.normalizedInput.remove.length > 0
).then(() => {
const variantId = transform({ normalizedInput }, (data) => {
return data.normalizedInput.variant_id
})
const { data: variant } = useQueryGraphStep({
entity: "variant",
fields: ["id", "thumbnail"],
filters: {
id: variantId,
},
options: {
isList: false,
},
}).config({ name: "get-variant-thumbnail" })
const removedImagesQuery = useQueryGraphStep({
entity: "product_image",
fields: ["id", "url"],
filters: {
id: normalizedInput.remove,
},
}).config({ name: "get-removed-images" })
const shouldUpdateVariantThumbnail = transform(
{ removedImagesQuery, variant },
(data) => {
const urls =
data.removedImagesQuery.data?.map((image) => image.url) ?? []
return !!urls.includes((data.variant as ProductVariantDTO).thumbnail)
}
)
return shouldUpdateVariantThumbnail
})
when(
"should-update-variant-thumbnail",
{ shouldUpdateVariantThumbnail },
(data) => !!data.shouldUpdateVariantThumbnail
).then(() =>
updateProductVariantsStep({
selector: { id: input.variant_id },
update: { thumbnail: null },
})
)
const response = transform({ res, input }, (data) => {
return {
added: data.res[0] ?? [],
removed: data.res[1] ?? [],
}
})
return new WorkflowResponse(response)
}
)
@@ -1,3 +1,5 @@
export * from "./batch-image-variants"
export * from "./batch-variant-images"
export * from "./batch-link-products-collection"
export * from "./batch-product-variants"
export * from "./batch-products"
+72
View File
@@ -1075,4 +1075,76 @@ export class Product {
}
)
}
/**
* This method manages image-variant associations for a specific image. It sends a request to the
* [Batch Image Variants](https://docs.medusajs.com/api/admin#products_postproductsidimagesimage_idvariantsbatch)
* API route.
*
* @param productId - The product's ID.
* @param imageId - The image's ID.
* @param body - The variants to add or remove from the image.
* @param headers - Headers to pass in the request
* @returns The batch operation details.
*
* @example
* sdk.admin.product.batchImageVariants("prod_123", "img_123", {
* add: ["variant_123", "variant_456"],
* remove: ["variant_789"]
* })
* .then(({ added, removed }) => {
* console.log(added, removed)
* })
*/
async batchImageVariants(
productId: string,
imageId: string,
body: HttpTypes.AdminBatchImageVariantRequest,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminBatchImageVariantResponse>(
`/admin/products/${productId}/images/${imageId}/variants/batch`,
{
method: "POST",
headers,
body,
}
)
}
/**
* This method manages variant-image associations for a specific variant. It sends a request to the
* [Batch Variant Images](https://docs.medusajs.com/api/admin#products_postproductsidvariantsvariant_idimagesbatch)
* API route.
*
* @param productId - The product's ID.
* @param variantId - The variant's ID.
* @param body - The images to add or remove from the variant.
* @param headers - Headers to pass in the request
* @returns The batch operation details.
*
* @example
* sdk.admin.product.batchVariantImages("prod_123", "variant_123", {
* add: ["img_123", "img_456"],
* remove: ["img_789"]
* })
* .then(({ added, removed }) => {
* console.log(added, removed)
* })
*/
async batchVariantImages(
productId: string,
variantId: string,
body: HttpTypes.AdminBatchVariantImagesRequest,
headers?: ClientHeaders
) {
return await this.client.fetch<HttpTypes.AdminBatchVariantImagesResponse>(
`/admin/products/${productId}/variants/${variantId}/images/batch`,
{
method: "POST",
headers,
body,
}
)
}
}
@@ -59,6 +59,11 @@ export interface AdminProductVariant extends BaseProductVariant {
* The variant's inventory items.
*/
inventory_items?: AdminProductVariantInventoryItemLink[] | null
/**
* The variant's images.
*/
images?: AdminProductImage[] | null
}
export interface AdminProductOption extends BaseProductOption {
/**
@@ -70,7 +75,16 @@ export interface AdminProductOption extends BaseProductOption {
*/
values?: AdminProductOptionValue[]
}
export interface AdminProductImage extends BaseProductImage {}
export interface AdminProductImage extends BaseProductImage {
/**
* The product that the image belongs to.
*/
product?: AdminProduct | null
/**
* The variants that the image is scoped to.
*/
variants?: AdminProductVariant[] | null
}
export interface AdminProductOptionValue extends BaseProductOptionValue {
/**
* The option's details.
@@ -316,6 +316,10 @@ export interface AdminUpdateProductVariant {
* The variant's MID code.
*/
mid_code?: string | null
/**
* The variant's thumbnail.
*/
thumbnail?: string | null
/**
* Whether the variant can be ordered even if it's out of stock.
*/
@@ -571,6 +575,28 @@ interface AdminDeleteProductVariantInventoryItem {
variant_id: string
}
export interface AdminBatchImageVariantRequest {
/**
* The variant IDs to add to the image.
*/
add?: string[]
/**
* The variant IDs to remove from the image.
*/
remove?: string[]
}
export interface AdminBatchVariantImagesRequest {
/**
* The image IDs to add to the variant.
*/
add?: string[]
/**
* The image IDs to remove from the variant.
*/
remove?: string[]
}
export interface AdminImportProductsRequest {
/**
* The file's identifier in the third-party system.
@@ -115,3 +115,25 @@ export interface AdminProductVariantInventoryLinkDeleteResponse {
deleted: boolean
parent: AdminProductVariant
}
export interface AdminBatchImageVariantResponse {
/**
* The variant IDs that were added to the image.
*/
added: string[]
/**
* The variant IDs that were removed from the image.
*/
removed: string[]
}
export interface AdminBatchVariantImagesResponse {
/**
* The image IDs that were added to the variant.
*/
added: string[]
/**
* The image IDs that were removed from the variant.
*/
removed: string[]
}
@@ -159,6 +159,10 @@ export interface BaseProductVariant {
* The variant's UPC.
*/
upc: string | null
/**
* The variant's thumbnail.
*/
thumbnail: string | null
/**
* Whether the variant can be ordered even if it's out of stock.
*/
+14
View File
@@ -192,6 +192,10 @@ export interface ProductVariantDTO {
* Whether the product variant's inventory should be managed by the core system.
*/
manage_inventory: boolean
/**
* The thumbnail of the product variant form the product images.
*/
thumbnail: string | null
/**
* Whether the product variant's requires shipping.
*/
@@ -234,6 +238,12 @@ export interface ProductVariantDTO {
* @expandable
*/
options: ProductOptionValueDTO[]
/**
* The associated product images.
*
* @expandable
*/
images: ProductImageDTO[]
/**
* Holds custom data in key-value pairs.
*/
@@ -1402,6 +1412,10 @@ export interface UpdateProductVariantDTO {
* The UPC of the product variant.
*/
upc?: string | null
/**
* The thumbnail of the product variant.
*/
thumbnail?: string | null
/**
* Whether the product variant can be ordered when it's out of stock.
*/
+91 -59
View File
@@ -68,12 +68,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -111,12 +111,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the products:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -172,12 +172,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the products:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -419,12 +419,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -462,12 +462,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product tags:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -523,12 +523,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product tags:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -1080,12 +1080,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -1185,14 +1185,14 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product options:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
*
* ```ts
* const [options, count] =
* await productModuleService.listAndCountProductOptions(
@@ -1452,12 +1452,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -1553,14 +1553,14 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product option values:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
*
* ```ts
* const [options, count] =
* await productModuleService.listAndCountProductOptionValues(
@@ -1772,12 +1772,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -1815,12 +1815,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product variants:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -1876,12 +1876,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product variants:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -2131,6 +2131,38 @@ export interface IProductModuleService extends IModuleService {
sharedContext?: Context
): Promise<Record<string, string[]> | void>
/**
* This method is used to associate images and variants.
*
* @param {DataTransferItemsFilter} data - Image variant pairs.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<{ id: string }[]>} The IDs of the image variant pairs.
*
* @example
* await productModuleService.addImageToVariant([
* {
* image_id: "img_123",
* variant_id: "variant_321",
* },
* ])
*/
addImageToVariant(
data: { image_id: string; variant_id: string }[],
sharedContext?: Context
): Promise<{ id: string }[]>
/**
* This method is used to remove images from variants.
*
* @param {DataTransferItemsFilter} data - Image variant pairs.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<void>} The IDs of the image variant pairs.
*/
removeImageFromVariant(
data: { image_id: string; variant_id: string }[],
sharedContext?: Context
): Promise<void>
/**
* This method is used to retrieve a product collection by its ID.
*
@@ -2150,12 +2182,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -2192,12 +2224,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product collections:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -2255,12 +2287,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product collections:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -2518,12 +2550,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -2561,12 +2593,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product categories:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts
@@ -2622,12 +2654,12 @@ export interface IProductModuleService extends IModuleService {
* ```
*
* To specify relations that should be retrieved within the product categories:
*
*
* :::note
*
*
* You can only retrieve data models defined in the same module. To retrieve linked data models
* from other modules, use [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) instead.
*
*
* :::
*
* ```ts