feat: add hasMany flag to enforce in app link uniqueness (#12039)

* feat: add createMultiple flag to enforce inApp link uniqueness

* changes

* mocks

* default

* many to many

---------

Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>
This commit is contained in:
Harminder VirkandCarlos R. L. Rodrigues authored and GitHub committed 2025-04-02 10:46:51 +02:00
1 parent f441362f4a
commit d3e725a907
25 files changed
+316 -70

No files matched your search

@@ -46,7 +46,7 @@ medusaIntegrationTestRunner({
entity: "Currency", entity: "Currency",
primaryKey: "code", primaryKey: "code",
foreignKey: "currency_code", foreignKey: "currency_code",
isList: true, hasMany: false,
alias: "currency", alias: "currency",
args: { args: {
methodSuffix: "Currencies", methodSuffix: "Currencies",
@@ -59,7 +59,7 @@ medusaIntegrationTestRunner({
primaryKey: "id", primaryKey: "id",
foreignKey: "region_id", foreignKey: "region_id",
alias: "region", alias: "region",
isList: false, hasMany: false,
args: { args: {
methodSuffix: "Regions", methodSuffix: "Regions",
}, },
@@ -90,9 +90,9 @@ medusaIntegrationTestRunner({
serviceName: "region", serviceName: "region",
entity: "Region", entity: "Region",
fieldAlias: { fieldAlias: {
currencies: { currency: {
path: "currency_link.currency", path: "currency_link.currency",
isList: true, isList: false,
forwardArgumentsOnPath: ["currency_link.currency"], forwardArgumentsOnPath: ["currency_link.currency"],
}, },
}, },
@@ -102,7 +102,7 @@ medusaIntegrationTestRunner({
primaryKey: "region_id", primaryKey: "region_id",
foreignKey: "id", foreignKey: "id",
alias: "currency_link", alias: "currency_link",
isList: true, isList: false,
}, },
}, },
], ],
@@ -147,7 +147,7 @@ medusaIntegrationTestRunner({
entity: "ProductVariant", entity: "ProductVariant",
primaryKey: "id", primaryKey: "id",
foreignKey: "product_variant_id", foreignKey: "product_variant_id",
isList: true, hasMany: false,
alias: "product_variant", alias: "product_variant",
args: { args: {
methodSuffix: "ProductVariants", methodSuffix: "ProductVariants",
@@ -159,7 +159,7 @@ medusaIntegrationTestRunner({
entity: "Region", entity: "Region",
primaryKey: "id", primaryKey: "id",
foreignKey: "region_id", foreignKey: "region_id",
isList: false, hasMany: false,
alias: "region", alias: "region",
args: { args: {
methodSuffix: "Regions", methodSuffix: "Regions",
@@ -191,9 +191,9 @@ medusaIntegrationTestRunner({
serviceName: "region", serviceName: "region",
entity: "Region", entity: "Region",
fieldAlias: { fieldAlias: {
product_variants: { product_variant: {
path: "product_variant_link.product_variant", path: "product_variant_link.product_variant",
isList: true, isList: false,
forwardArgumentsOnPath: [ forwardArgumentsOnPath: [
"product_variant_link.product_variant", "product_variant_link.product_variant",
], ],
@@ -205,7 +205,7 @@ medusaIntegrationTestRunner({
primaryKey: "region_id", primaryKey: "region_id",
foreignKey: "id", foreignKey: "id",
alias: "product_variant_link", alias: "product_variant_link",
isList: true, isList: false,
}, },
}, },
], ],
@@ -253,7 +253,7 @@ medusaIntegrationTestRunner({
entity: "Currency", entity: "Currency",
primaryKey: "code", primaryKey: "code",
foreignKey: "currency_code", foreignKey: "currency_code",
isList: true, hasMany: false,
alias: "currency", alias: "currency",
args: { args: {
methodSuffix: "Currencies", methodSuffix: "Currencies",
@@ -265,7 +265,7 @@ medusaIntegrationTestRunner({
entity: "Region", entity: "Region",
primaryKey: "id", primaryKey: "id",
foreignKey: "region_id", foreignKey: "region_id",
isList: false, hasMany: false,
alias: "region", alias: "region",
args: { args: {
methodSuffix: "Regions", methodSuffix: "Regions",
@@ -297,9 +297,9 @@ medusaIntegrationTestRunner({
serviceName: "region", serviceName: "region",
entity: "Region", entity: "Region",
fieldAlias: { fieldAlias: {
currencies: { currency: {
path: "currency_link.currency", path: "currency_link.currency",
isList: true, isList: false,
forwardArgumentsOnPath: ["currency_link.currency"], forwardArgumentsOnPath: ["currency_link.currency"],
}, },
}, },
@@ -309,7 +309,7 @@ medusaIntegrationTestRunner({
primaryKey: "region_id", primaryKey: "region_id",
foreignKey: "id", foreignKey: "id",
alias: "currency_link", alias: "currency_link",
isList: true, isList: false,
}, },
}, },
], ],
@@ -353,7 +353,7 @@ medusaIntegrationTestRunner({
entity: "Currency", entity: "Currency",
primaryKey: "code", primaryKey: "code",
foreignKey: "currency_code", foreignKey: "currency_code",
isList: true, hasMany: false,
alias: "currency", alias: "currency",
args: { args: {
methodSuffix: "Currencies", methodSuffix: "Currencies",
@@ -365,7 +365,113 @@ medusaIntegrationTestRunner({
entity: "Region", entity: "Region",
primaryKey: "id", primaryKey: "id",
foreignKey: "region_id", foreignKey: "region_id",
isList: true, hasMany: true,
alias: "region",
args: {
methodSuffix: "Regions",
},
deleteCascade: false,
},
],
extends: [
{
serviceName: "currency",
entity: "Currency",
fieldAlias: {
regions: {
path: "region_link.region",
isList: true,
forwardArgumentsOnPath: ["region_link.region"],
},
},
relationship: {
serviceName: "CurrencyCurrencyRegionRegionLink",
entity: "LinkCurrencyCurrencyRegionRegion",
primaryKey: "currency_code",
foreignKey: "code",
alias: "region_link",
isList: true,
},
},
{
serviceName: "region",
entity: "Region",
fieldAlias: {
currency: {
path: "currency_link.currency",
isList: false,
forwardArgumentsOnPath: ["currency_link.currency"],
},
},
relationship: {
serviceName: "CurrencyCurrencyRegionRegionLink",
entity: "LinkCurrencyCurrencyRegionRegion",
primaryKey: "region_id",
foreignKey: "id",
alias: "currency_link",
isList: false,
},
},
],
})
})
it("should generate a proper link with both sides using explicit isList=true", async () => {
const currencyLinks = CurrencyModule.linkable
const regionLinks = RegionModule.linkable
const link = defineLink(
{
linkable: currencyLinks.currency,
isList: true,
},
{
linkable: regionLinks.region,
isList: true,
}
)
const linkDefinition = MedusaModule.getCustomLinks()
.map((linkDefinition: any) => {
const definition = linkDefinition(
MedusaModule.getAllJoinerConfigs()
)
return definition.serviceName === link.serviceName && definition
})
.filter(Boolean)[0]
expect(link.serviceName).toEqual("CurrencyCurrencyRegionRegionLink")
expect(linkDefinition).toEqual({
serviceName: "CurrencyCurrencyRegionRegionLink",
isLink: true,
alias: [
{
name: ["currency_region"],
args: {
entity: "LinkCurrencyCurrencyRegionRegion",
},
},
],
primaryKeys: ["id", "currency_code", "region_id"],
relationships: [
{
serviceName: "currency",
entity: "Currency",
primaryKey: "code",
foreignKey: "currency_code",
hasMany: true,
alias: "currency",
args: {
methodSuffix: "Currencies",
},
deleteCascade: false,
},
{
serviceName: "region",
entity: "Region",
primaryKey: "id",
foreignKey: "region_id",
hasMany: true,
alias: "region", alias: "region",
args: { args: {
methodSuffix: "Regions", methodSuffix: "Regions",
@@ -415,6 +521,112 @@ medusaIntegrationTestRunner({
], ],
}) })
}) })
it("should generate a proper link with both sides using explicit isList=false", async () => {
const currencyLinks = CurrencyModule.linkable
const regionLinks = RegionModule.linkable
const link = defineLink(
{
linkable: currencyLinks.currency,
isList: false,
},
{
linkable: regionLinks.region,
isList: false,
}
)
const linkDefinition = MedusaModule.getCustomLinks()
.map((linkDefinition: any) => {
const definition = linkDefinition(
MedusaModule.getAllJoinerConfigs()
)
return definition.serviceName === link.serviceName && definition
})
.filter(Boolean)[0]
expect(link.serviceName).toEqual("CurrencyCurrencyRegionRegionLink")
expect(linkDefinition).toEqual({
serviceName: "CurrencyCurrencyRegionRegionLink",
isLink: true,
alias: [
{
name: ["currency_region"],
args: {
entity: "LinkCurrencyCurrencyRegionRegion",
},
},
],
primaryKeys: ["id", "currency_code", "region_id"],
relationships: [
{
serviceName: "currency",
entity: "Currency",
primaryKey: "code",
foreignKey: "currency_code",
hasMany: false,
alias: "currency",
args: {
methodSuffix: "Currencies",
},
deleteCascade: false,
},
{
serviceName: "region",
entity: "Region",
primaryKey: "id",
foreignKey: "region_id",
hasMany: false,
alias: "region",
args: {
methodSuffix: "Regions",
},
deleteCascade: false,
},
],
extends: [
{
serviceName: "currency",
entity: "Currency",
fieldAlias: {
region: {
path: "region_link.region",
isList: false,
forwardArgumentsOnPath: ["region_link.region"],
},
},
relationship: {
serviceName: "CurrencyCurrencyRegionRegionLink",
entity: "LinkCurrencyCurrencyRegionRegion",
primaryKey: "currency_code",
foreignKey: "code",
alias: "region_link",
isList: false,
},
},
{
serviceName: "region",
entity: "Region",
fieldAlias: {
currency: {
path: "currency_link.currency",
isList: false,
forwardArgumentsOnPath: ["currency_link.currency"],
},
},
relationship: {
serviceName: "CurrencyCurrencyRegionRegionLink",
entity: "LinkCurrencyCurrencyRegionRegion",
primaryKey: "region_id",
foreignKey: "id",
alias: "currency_link",
isList: false,
},
},
],
})
})
}) })
}, },
}) })
@@ -21,5 +21,6 @@ export const InventoryModule = {
}, },
}, },
list: jest.fn(async () => []),
softDelete: jest.fn(() => {}), softDelete: jest.fn(() => {}),
} }
@@ -65,5 +65,6 @@ export const InventoryStockLocationLink = {
foreignKeyData?: string foreignKeyData?: string
) => {} ) => {}
), ),
list: jest.fn(async () => []),
softDelete: jest.fn(() => {}), softDelete: jest.fn(() => {}),
} }
@@ -71,5 +71,6 @@ export const ProductInventoryLinkModule = {
foreignKeyData?: string foreignKeyData?: string
) => {} ) => {}
), ),
list: jest.fn(async () => []),
softDelete: jest.fn(() => {}), softDelete: jest.fn(() => {}),
} }
@@ -18,5 +18,6 @@ export const ProductModule = {
alias: [], alias: [],
}, },
list: jest.fn(async () => []),
softDelete: jest.fn(() => {}), softDelete: jest.fn(() => {}),
} }
@@ -18,5 +18,6 @@ export const StockLocationModule = {
alias: [], alias: [],
}, },
list: jest.fn(async () => []),
softDelete: jest.fn(() => {}), softDelete: jest.fn(() => {}),
} }
+40 -44
View File
@@ -389,7 +389,7 @@ export class Link {
{ {
linksToCreate: [string | string[], string, Record<string, unknown>?][] linksToCreate: [string | string[], string, Record<string, unknown>?][]
linksToValidateForUniqueness: { linksToValidateForUniqueness: {
filters: { [key: string]: string }[] filters: { [key: string]: any }[]
services: string[] services: string[]
} }
} }
@@ -418,55 +418,51 @@ export class Link {
*/ */
linksToValidateForUniqueness: { linksToValidateForUniqueness: {
filters: [], filters: [],
services: [], services: relationships?.map((r) => r.serviceName) ?? [],
}, },
}) })
} }
relationships?.forEach((relationship) => { /**
const linksToValidateForUniqueness = serviceLinks.get( * When isList is set on false on the relationship, then it means
service.__definition.key * we have a one-to-one or many-to-one relationship with the
)!.linksToValidateForUniqueness! * other side and we have limit duplicate entries from other
* entity. For example:
*
* - A brand has a many to one relationship with a product.
* - A product can have only one brand. Aka (brand.isList = false)
* - A brand can have multiple products. Aka (products.isList = true)
*
* A result of this, we have to ensure that a product_id can only appear
* once in the pivot table that is used for tracking "brand<>products"
* relationship.
*/
const linksToValidateForUniqueness = serviceLinks.get(
service.__definition.key
)!.linksToValidateForUniqueness!
linksToValidateForUniqueness.services.push(relationship.serviceName) const modA = relationships?.[0]!
const modB = relationships?.[1]!
/** if (!modA.hasMany || !modB.hasMany) {
* When isList is set on false on the relationship, then it means if (!modA.hasMany && !modB.hasMany) {
* we have a one-to-one or many-to-one relationship with the linksToValidateForUniqueness.filters.push({
* other side and we have limit duplicate entries from other $or: [
* entity. For example: { [modA.foreignKey]: link[moduleA][modA.foreignKey] },
* { [modB.foreignKey]: link[moduleB][modB.foreignKey] },
* - A brand has a many to one relationship with a product. ],
* - A product can have only one brand. Aka (brand.isList = false) })
* - A brand can have multiple products. Aka (products.isList = true) } else if (!modA.hasMany) {
* linksToValidateForUniqueness.filters.push({
* A result of this, we have to ensure that a product_id can only appear [modA.foreignKey]: { $ne: link[moduleA][modA.foreignKey] },
* once in the pivot table that is used for tracking "brand<>products" [modB.foreignKey]: link[moduleB][modB.foreignKey],
* relationship. })
*/ } else if (!modB.hasMany) {
if (relationship.isList === false) { linksToValidateForUniqueness.filters.push({
const otherSide = relationships.find( [modB.foreignKey]: { $ne: link[moduleB][modB.foreignKey] },
(other) => other.foreignKey !== relationship.foreignKey [modA.foreignKey]: link[moduleA][modA.foreignKey],
) })
if (!otherSide) {
return
}
if (moduleBKey === otherSide.foreignKey) {
linksToValidateForUniqueness.filters.push({
[otherSide.foreignKey]: link[moduleB][moduleBKey],
})
} else {
primaryKeys.forEach((pk) => {
if (pk === otherSide.foreignKey) {
linksToValidateForUniqueness.filters.push({
[otherSide.foreignKey]: link[moduleA][pk],
})
}
})
}
} }
}) }
const pkValue = const pkValue =
primaryKeys.length === 1 primaryKeys.length === 1
+6 -1
View File
@@ -209,7 +209,7 @@ export type ModuleJoinerConfig = Omit<
isList?: boolean isList?: boolean
} }
> // alias for deeper nested relationships (e.g. { 'price': 'prices.calculated_price_set.amount' }) > // alias for deeper nested relationships (e.g. { 'price': 'prices.calculated_price_set.amount' })
relationship: ModuleJoinerRelationship relationship: Omit<ModuleJoinerRelationship, "hasMany">
}[] }[]
serviceName?: string serviceName?: string
primaryKeys?: string[] primaryKeys?: string[]
@@ -248,6 +248,11 @@ export declare type ModuleJoinerRelationship = JoinerRelationship & {
* If true, the link joiner will cascade deleting the relationship * If true, the link joiner will cascade deleting the relationship
*/ */
deleteCascade?: boolean deleteCascade?: boolean
/**
* Allow multiple relationships to exist for this
* entity
*/
hasMany?: boolean
} }
export type ModuleExports<T = Constructor<any>> = { export type ModuleExports<T = Constructor<any>> = {
@@ -85,6 +85,7 @@ type ModuleLinkableKeyConfig = {
deleteCascade?: boolean deleteCascade?: boolean
primaryKey: string primaryKey: string
alias: string alias: string
hasMany?: boolean
shortcut?: Shortcut | Shortcut[] shortcut?: Shortcut | Shortcut[]
} }
@@ -125,8 +126,7 @@ function buildFieldAlias(fieldAliases?: Shortcut | Shortcut[]) {
} }
function prepareServiceConfig( function prepareServiceConfig(
input: DefineLinkInputSource | DefineReadOnlyLinkInputSource, input: DefineLinkInputSource | DefineReadOnlyLinkInputSource
defaultOptions?: { isList?: boolean }
) { ) {
let serviceConfig = {} as ModuleLinkableKeyConfig let serviceConfig = {} as ModuleLinkableKeyConfig
@@ -138,7 +138,8 @@ function prepareServiceConfig(
alias: source.alias ?? camelToSnakeCase(source.field ?? ""), alias: source.alias ?? camelToSnakeCase(source.field ?? ""),
field: input.field ?? source.field, field: input.field ?? source.field,
primaryKey: source.primaryKey, primaryKey: source.primaryKey,
isList: defaultOptions?.isList ?? false, isList: false,
hasMany: false,
deleteCascade: false, deleteCascade: false,
module: source.serviceName, module: source.serviceName,
entity: source.entity, entity: source.entity,
@@ -148,12 +149,15 @@ function prepareServiceConfig(
? input.linkable.toJSON() ? input.linkable.toJSON()
: input.linkable : input.linkable
const hasMany = !!input.isList
serviceConfig = { serviceConfig = {
key: source.linkable, key: source.linkable,
alias: source.alias ?? camelToSnakeCase(source.field ?? ""), alias: source.alias ?? camelToSnakeCase(source.field ?? ""),
field: input.field ?? source.field, field: input.field ?? source.field,
primaryKey: source.primaryKey, primaryKey: source.primaryKey,
isList: input.isList ?? defaultOptions?.isList ?? false, isList: input.isList ?? false,
hasMany,
deleteCascade: input.deleteCascade ?? false, deleteCascade: input.deleteCascade ?? false,
module: source.serviceName, module: source.serviceName,
entity: source.entity, entity: source.entity,
@@ -184,8 +188,8 @@ export function defineLink(
rightService: DefineLinkInputSource | DefineReadOnlyLinkInputSource, rightService: DefineLinkInputSource | DefineReadOnlyLinkInputSource,
linkServiceOptions?: ExtraOptions | ReadOnlyExtraOptions linkServiceOptions?: ExtraOptions | ReadOnlyExtraOptions
): DefineLinkExport { ): DefineLinkExport {
const serviceAObj = prepareServiceConfig(leftService, { isList: true }) const serviceAObj = prepareServiceConfig(leftService)
const serviceBObj = prepareServiceConfig(rightService, { isList: false }) const serviceBObj = prepareServiceConfig(rightService)
if (linkServiceOptions?.readOnly) { if (linkServiceOptions?.readOnly) {
return defineReadOnlyLink( return defineReadOnlyLink(
@@ -374,7 +378,7 @@ ${serviceBObj.module}: {
methodSuffix: serviceAMethodSuffix, methodSuffix: serviceAMethodSuffix,
}, },
deleteCascade: serviceAObj.deleteCascade, deleteCascade: serviceAObj.deleteCascade,
isList: serviceAObj.isList, hasMany: serviceAObj.hasMany,
}, },
{ {
serviceName: serviceBObj.module, serviceName: serviceBObj.module,
@@ -386,7 +390,7 @@ ${serviceBObj.module}: {
methodSuffix: serviceBMethodSuffix, methodSuffix: serviceBMethodSuffix,
}, },
deleteCascade: serviceBObj.deleteCascade, deleteCascade: serviceBObj.deleteCascade,
isList: serviceBObj.isList, hasMany: serviceBObj.hasMany,
}, },
], ],
extends: [ extends: [
@@ -25,6 +25,7 @@ export const CartPromotion: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Carts", methodSuffix: "Carts",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.PROMOTION, serviceName: Modules.PROMOTION,
@@ -35,6 +36,7 @@ export const CartPromotion: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Promotions", methodSuffix: "Promotions",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -35,6 +35,7 @@ export const CustomerAccountHolder: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "AccountHolders", methodSuffix: "AccountHolders",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -23,6 +23,7 @@ export const LocationFulfillmentProvider: ModuleJoinerConfig = {
foreignKey: "stock_location_id", foreignKey: "stock_location_id",
alias: "location", alias: "location",
args: { methodSuffix: "StockLocations" }, args: { methodSuffix: "StockLocations" },
hasMany: true,
}, },
{ {
serviceName: Modules.FULFILLMENT, serviceName: Modules.FULFILLMENT,
@@ -31,6 +32,7 @@ export const LocationFulfillmentProvider: ModuleJoinerConfig = {
foreignKey: "fulfillment_provider_id", foreignKey: "fulfillment_provider_id",
alias: "fulfillment_provider", alias: "fulfillment_provider",
args: { methodSuffix: "FulfillmentProviders" }, args: { methodSuffix: "FulfillmentProviders" },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -36,6 +36,7 @@ export const LocationFulfillmentSet: ModuleJoinerConfig = {
methodSuffix: "FulfillmentSets", methodSuffix: "FulfillmentSets",
}, },
deleteCascade: true, deleteCascade: true,
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -38,6 +38,7 @@ export const OrderClaimPaymentCollection: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "PaymentCollections", methodSuffix: "PaymentCollections",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -38,6 +38,7 @@ export const OrderExchangePaymentCollection: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "PaymentCollections", methodSuffix: "PaymentCollections",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -35,6 +35,7 @@ export const OrderFulfillment: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Fulfillments", methodSuffix: "Fulfillments",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -36,6 +36,7 @@ export const OrderPaymentCollection: ModuleJoinerConfig = {
methodSuffix: "PaymentCollections", methodSuffix: "PaymentCollections",
}, },
deleteCascade: true, deleteCascade: true,
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -25,6 +25,7 @@ export const OrderPromotion: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Orders", methodSuffix: "Orders",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.PROMOTION, serviceName: Modules.PROMOTION,
@@ -35,6 +36,7 @@ export const OrderPromotion: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Promotions", methodSuffix: "Promotions",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -35,6 +35,7 @@ export const ReturnFulfillment: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Fulfillments", methodSuffix: "Fulfillments",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -27,6 +27,7 @@ export const ProductSalesChannel: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Products", methodSuffix: "Products",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.SALES_CHANNEL, serviceName: Modules.SALES_CHANNEL,
@@ -37,6 +38,7 @@ export const ProductSalesChannel: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "SalesChannels", methodSuffix: "SalesChannels",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -27,6 +27,7 @@ export const ProductShippingProfile: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Products", methodSuffix: "Products",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.FULFILLMENT, serviceName: Modules.FULFILLMENT,
@@ -34,6 +34,7 @@ export const ProductVariantInventoryItem: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "ProductVariants", methodSuffix: "ProductVariants",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.INVENTORY, serviceName: Modules.INVENTORY,
@@ -44,6 +45,7 @@ export const ProductVariantInventoryItem: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "InventoryItems", methodSuffix: "InventoryItems",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -27,6 +27,7 @@ export const PublishableApiKeySalesChannel: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "ApiKeys", methodSuffix: "ApiKeys",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.SALES_CHANNEL, serviceName: Modules.SALES_CHANNEL,
@@ -37,6 +38,7 @@ export const PublishableApiKeySalesChannel: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "SalesChannels", methodSuffix: "SalesChannels",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -25,6 +25,7 @@ export const RegionPaymentProvider: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "Regions", methodSuffix: "Regions",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.PAYMENT, serviceName: Modules.PAYMENT,
@@ -33,6 +34,7 @@ export const RegionPaymentProvider: ModuleJoinerConfig = {
foreignKey: "payment_provider_id", foreignKey: "payment_provider_id",
alias: "payment_provider", alias: "payment_provider",
args: { methodSuffix: "PaymentProviders" }, args: { methodSuffix: "PaymentProviders" },
hasMany: true,
}, },
], ],
extends: [ extends: [
@@ -25,6 +25,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "SalesChannels", methodSuffix: "SalesChannels",
}, },
hasMany: true,
}, },
{ {
serviceName: Modules.STOCK_LOCATION, serviceName: Modules.STOCK_LOCATION,
@@ -35,6 +36,7 @@ export const SalesChannelLocation: ModuleJoinerConfig = {
args: { args: {
methodSuffix: "StockLocations", methodSuffix: "StockLocations",
}, },
hasMany: true,
}, },
], ],
extends: [ extends: [