feat(modules-sdk): Parse filters based on loaded modules graph (#9158)
This commit is contained in:
1 parent
812b80b6a3
commit
c6795dfc47
12 files changed
+1206
-93
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
import { IRegionModuleService } from "@medusajs/types"
|
import { IRegionModuleService, RemoteQueryFunction } from "@medusajs/types"
|
||||||
import { ContainerRegistrationKeys, Modules } from "@medusajs/utils"
|
import { ContainerRegistrationKeys, Modules } from "@medusajs/utils"
|
||||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
import { createAdminUser } from "../../..//helpers/create-admin-user"
|
import { createAdminUser } from "../../..//helpers/create-admin-user"
|
||||||
@@ -195,5 +195,103 @@ medusaIntegrationTestRunner({
|
|||||||
).resolves.toHaveLength(1)
|
).resolves.toHaveLength(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Query", () => {
|
||||||
|
let appContainer
|
||||||
|
let query: RemoteQueryFunction
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
appContainer = getContainer()
|
||||||
|
query = appContainer.resolve(ContainerRegistrationKeys.QUERY)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: "Test Giftcard",
|
||||||
|
is_giftcard: true,
|
||||||
|
description: "test-giftcard-description",
|
||||||
|
options: [{ title: "Denominations", values: ["100"] }],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: "Test variant",
|
||||||
|
prices: [{ currency_code: "usd", amount: 100 }],
|
||||||
|
options: {
|
||||||
|
Denominations: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
await api
|
||||||
|
.post("/admin/products", payload, adminHeaders)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should perform cross module query and apply filters correctly to the correct modules [1]`, async () => {
|
||||||
|
const { data } = await query.graph({
|
||||||
|
entity: "product",
|
||||||
|
fields: ["id", "title", "variants.*", "variants.prices.amount"],
|
||||||
|
filters: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: {
|
||||||
|
$gt: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(data).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
title: "Test Giftcard",
|
||||||
|
variants: [
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Test variant",
|
||||||
|
prices: [],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`should perform cross module query and apply filters correctly to the correct modules [2]`, async () => {
|
||||||
|
const { data: dataWithPrice } = await query.graph({
|
||||||
|
entity: "product",
|
||||||
|
fields: ["id", "title", "variants.*", "variants.prices.amount"],
|
||||||
|
filters: {
|
||||||
|
variants: {
|
||||||
|
prices: {
|
||||||
|
amount: {
|
||||||
|
$gt: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(dataWithPrice).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
title: "Test Giftcard",
|
||||||
|
variants: [
|
||||||
|
expect.objectContaining({
|
||||||
|
title: "Test variant",
|
||||||
|
prices: [
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: 100,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -413,10 +413,12 @@ async function MedusaApp_({
|
|||||||
|
|
||||||
const loadedSchema = getLoadedSchema()
|
const loadedSchema = getLoadedSchema()
|
||||||
const { schema, notFound } = cleanAndMergeSchema(loadedSchema)
|
const { schema, notFound } = cleanAndMergeSchema(loadedSchema)
|
||||||
|
const entitiesMap = schema.getTypeMap() as unknown as Map<string, any>
|
||||||
|
|
||||||
const remoteQuery = new RemoteQuery({
|
const remoteQuery = new RemoteQuery({
|
||||||
servicesConfig,
|
servicesConfig,
|
||||||
customRemoteFetchData: remoteFetchData,
|
customRemoteFetchData: remoteFetchData,
|
||||||
|
entitiesMap,
|
||||||
})
|
})
|
||||||
|
|
||||||
const applyMigration = async ({
|
const applyMigration = async ({
|
||||||
@@ -521,7 +523,7 @@ async function MedusaApp_({
|
|||||||
modules: allModules,
|
modules: allModules,
|
||||||
link: remoteLink,
|
link: remoteLink,
|
||||||
query: createQuery(remoteQuery) as any, // TODO: rm any once we remove the old RemoteQueryFunction and rely on the Query object instead,
|
query: createQuery(remoteQuery) as any, // TODO: rm any once we remove the old RemoteQueryFunction and rely on the Query object instead,
|
||||||
entitiesMap: schema.getTypeMap(),
|
entitiesMap,
|
||||||
gqlSchema: schema,
|
gqlSchema: schema,
|
||||||
notFound,
|
notFound,
|
||||||
runMigrations,
|
runMigrations,
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { mergeTypeDefs } from "@graphql-tools/merge"
|
||||||
|
import { makeExecutableSchema } from "@graphql-tools/schema"
|
||||||
|
import { cleanGraphQLSchema } from "../../utils/clean-graphql-schema"
|
||||||
|
|
||||||
|
export function getEntitiesMap(loadedSchema): Map<string, any> {
|
||||||
|
const defaultMedusaSchema = `
|
||||||
|
scalar DateTime
|
||||||
|
scalar JSON
|
||||||
|
`
|
||||||
|
const { schema } = cleanGraphQLSchema(defaultMedusaSchema + loadedSchema)
|
||||||
|
const mergedSchema = mergeTypeDefs(schema)
|
||||||
|
return makeExecutableSchema({ typeDefs: mergedSchema }).getTypeMap() as any
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import { defineJoinerConfig } from "@medusajs/utils"
|
||||||
|
import { MedusaModule } from "../../medusa-module"
|
||||||
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
|
||||||
|
const productJoinerConfig = defineJoinerConfig("product", {
|
||||||
|
schema: `
|
||||||
|
type Product {
|
||||||
|
id: ID
|
||||||
|
title: String
|
||||||
|
variants: [Variant]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Variant {
|
||||||
|
id: ID
|
||||||
|
sku: String
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: ["product"],
|
||||||
|
entity: "Product",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "Products",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ["variant", "variants"],
|
||||||
|
entity: "Variant",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "Variants",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const pricingJoinerConfig = defineJoinerConfig("pricing", {
|
||||||
|
schema: `
|
||||||
|
type PriceSet {
|
||||||
|
id: ID
|
||||||
|
prices: [Price]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Price {
|
||||||
|
amount: Int
|
||||||
|
deep_nested_price: DeepNestedPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeepNestedPrice {
|
||||||
|
amount: Int
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: ["price", "prices"],
|
||||||
|
entity: "Price",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "price",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ["price_set", "price_sets"],
|
||||||
|
entity: "PriceSet",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "priceSet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ["deep_nested_price", "deep_nested_prices"],
|
||||||
|
entity: "DeepNestedPrice",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "deepNestedPrice",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const linkProductVariantPriceSet = {
|
||||||
|
serviceName: "link-product-variant-price-set",
|
||||||
|
isLink: true,
|
||||||
|
databaseConfig: {
|
||||||
|
tableName: "product_variant_price_set",
|
||||||
|
idPrefix: "pvps",
|
||||||
|
},
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: ["product_variant_price_set", "product_variant_price_sets"],
|
||||||
|
entity: "LinkProductVariantPriceSet",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primaryKeys: ["id", "variant_id", "price_set_id"],
|
||||||
|
relationships: [
|
||||||
|
{
|
||||||
|
serviceName: "product",
|
||||||
|
entity: "ProductVariant",
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "variant_id",
|
||||||
|
alias: "variant",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "ProductVariants",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: "pricing",
|
||||||
|
entity: "PriceSet",
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "price_set_id",
|
||||||
|
alias: "price_set",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "PriceSets",
|
||||||
|
},
|
||||||
|
deleteCascade: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
{
|
||||||
|
serviceName: "product",
|
||||||
|
fieldAlias: {
|
||||||
|
price_set: "price_set_link.price_set",
|
||||||
|
prices: {
|
||||||
|
path: "price_set_link.price_set.prices",
|
||||||
|
isList: true,
|
||||||
|
forwardArgumentsOnPath: ["price_set_link.price_set"],
|
||||||
|
},
|
||||||
|
deep_nested_price: {
|
||||||
|
path: "price_set_link.price_set.deep_nested_price",
|
||||||
|
forwardArgumentsOnPath: ["price_set_link.price_set"],
|
||||||
|
},
|
||||||
|
calculated_price: {
|
||||||
|
path: "price_set_link.price_set.calculated_price",
|
||||||
|
forwardArgumentsOnPath: ["price_set_link.price_set"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
relationship: {
|
||||||
|
serviceName: "link-product-variant-price-set",
|
||||||
|
primaryKey: "variant_id",
|
||||||
|
foreignKey: "id",
|
||||||
|
alias: "price_set_link",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: "pricing",
|
||||||
|
relationship: {
|
||||||
|
serviceName: "link-product-variant-price-set",
|
||||||
|
primaryKey: "price_set_id",
|
||||||
|
foreignKey: "id",
|
||||||
|
alias: "variant_link",
|
||||||
|
},
|
||||||
|
fieldAlias: {
|
||||||
|
variant: "variant_link.variant",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as ModuleJoinerConfig
|
||||||
|
|
||||||
|
MedusaModule.setJoinerConfig("product", productJoinerConfig)
|
||||||
|
MedusaModule.setJoinerConfig("pricing", pricingJoinerConfig)
|
||||||
|
MedusaModule.setJoinerConfig(
|
||||||
|
"link-product-variant-price-set",
|
||||||
|
linkProductVariantPriceSet
|
||||||
|
)
|
||||||
@@ -0,0 +1,548 @@
|
|||||||
|
import { MedusaModule } from "../../medusa-module"
|
||||||
|
import { getEntitiesMap } from "../__fixtures__/get-entities-map"
|
||||||
|
import "../__fixtures__/parse-filters"
|
||||||
|
import { parseAndAssignFilters } from "../parse-filters"
|
||||||
|
|
||||||
|
const entitiesMap = getEntitiesMap(
|
||||||
|
MedusaModule.getAllJoinerConfigs()
|
||||||
|
.map((m) => m.schema)
|
||||||
|
.join("\n")
|
||||||
|
)
|
||||||
|
|
||||||
|
describe("parse-filters", () => {
|
||||||
|
describe("Without operator map usage", () => {
|
||||||
|
it("should parse filter for a single level module", () => {
|
||||||
|
const filters = {
|
||||||
|
id: "string",
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should parse filters through linked immediate relations", () => {
|
||||||
|
const filters = {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
price_set: {
|
||||||
|
id: "id_test",
|
||||||
|
prices: {
|
||||||
|
amount: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
price_set: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
price_set: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "id_test",
|
||||||
|
prices: {
|
||||||
|
amount: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should parse filters through linked nested relations through configured field alias with forward args", () => {
|
||||||
|
const filters = {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
prices: {
|
||||||
|
amount: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
prices: {
|
||||||
|
amount: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should parse filters through linked deep nested relations through configured field alias with forward args", () => {
|
||||||
|
const filters = {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
prices: {
|
||||||
|
amount: 50,
|
||||||
|
|
||||||
|
deep_nested_price: {
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
|
||||||
|
deep_nested_price: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
prices: {
|
||||||
|
amount: 50,
|
||||||
|
deep_nested_price: {
|
||||||
|
amount: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
deep_nested_price: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("With operator map usage", () => {
|
||||||
|
it("should parse filter for a single level module", () => {
|
||||||
|
const filters = {
|
||||||
|
id: { $ilike: "%string%" },
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: { $ilike: "%string%" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should parse filters through linked immediate relations", () => {
|
||||||
|
const filters = {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
price_set: {
|
||||||
|
id: "id_test",
|
||||||
|
prices: {
|
||||||
|
amount: {
|
||||||
|
$gte: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
price_set: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
price_set: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "id_test",
|
||||||
|
prices: {
|
||||||
|
amount: {
|
||||||
|
$gte: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should parse filters through linked nested relations through configured field alias with forward args", () => {
|
||||||
|
const filters = {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
prices: {
|
||||||
|
amount: {
|
||||||
|
$lt: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
prices: {
|
||||||
|
amount: { $lt: 50 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should parse filters through linked deep nested relations through configured field alias with forward args", () => {
|
||||||
|
const filters = {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
prices: {
|
||||||
|
deep_nested_price: {
|
||||||
|
amount: {
|
||||||
|
$gte: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQueryObject = {
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
|
||||||
|
deep_nested_price: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
remoteQueryObject,
|
||||||
|
entryPoint: "product",
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(remoteQueryObject).toEqual({
|
||||||
|
product: {
|
||||||
|
fields: ["id", "title", "variants"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
id: "string",
|
||||||
|
variants: {
|
||||||
|
sku: {
|
||||||
|
$eq: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
fields: ["id", "sku", "prices"],
|
||||||
|
|
||||||
|
prices: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
__args: {
|
||||||
|
filters: {
|
||||||
|
prices: {
|
||||||
|
deep_nested_price: {
|
||||||
|
amount: { $gte: 100 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
deep_nested_price: {
|
||||||
|
fields: ["id", "amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,18 +1,30 @@
|
|||||||
import { QueryContext, QueryFilter } from "@medusajs/utils"
|
import { QueryContext } from "@medusajs/utils"
|
||||||
|
import { MedusaModule } from "../../medusa-module"
|
||||||
|
import { getEntitiesMap } from "../__fixtures__/get-entities-map"
|
||||||
|
import "../__fixtures__/parse-filters"
|
||||||
import "../__fixtures__/remote-query-type"
|
import "../__fixtures__/remote-query-type"
|
||||||
import { toRemoteQuery } from "../to-remote-query"
|
import { toRemoteQuery } from "../to-remote-query"
|
||||||
|
|
||||||
|
const entitiesMap = getEntitiesMap(
|
||||||
|
MedusaModule.getAllJoinerConfigs()
|
||||||
|
.map((m) => m.schema)
|
||||||
|
.join("\n")
|
||||||
|
)
|
||||||
|
|
||||||
describe("toRemoteQuery", () => {
|
describe("toRemoteQuery", () => {
|
||||||
it("should transform a query with top level filtering", () => {
|
it("should transform a query with top level filtering", () => {
|
||||||
const format = toRemoteQuery({
|
const format = toRemoteQuery(
|
||||||
entity: "product",
|
{
|
||||||
fields: ["id", "handle", "description"],
|
entity: "product",
|
||||||
filters: QueryFilter<"product">({
|
fields: ["id", "handle", "description"],
|
||||||
handle: {
|
filters: {
|
||||||
$ilike: "abc%",
|
handle: {
|
||||||
|
$ilike: "abc%",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
})
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
expect(format).toEqual({
|
expect(format).toEqual({
|
||||||
product: {
|
product: {
|
||||||
@@ -29,14 +41,17 @@ describe("toRemoteQuery", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should transform a query with pagination", () => {
|
it("should transform a query with pagination", () => {
|
||||||
const format = toRemoteQuery({
|
const format = toRemoteQuery(
|
||||||
entity: "product",
|
{
|
||||||
fields: ["id", "handle", "description"],
|
entity: "product",
|
||||||
pagination: {
|
fields: ["id", "handle", "description"],
|
||||||
skip: 5,
|
pagination: {
|
||||||
take: 10,
|
skip: 5,
|
||||||
|
take: 10,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
expect(format).toEqual({
|
expect(format).toEqual({
|
||||||
product: {
|
product: {
|
||||||
@@ -50,19 +65,22 @@ describe("toRemoteQuery", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should transform a query with top level filtering and pagination", () => {
|
it("should transform a query with top level filtering and pagination", () => {
|
||||||
const format = toRemoteQuery({
|
const format = toRemoteQuery(
|
||||||
entity: "product",
|
{
|
||||||
fields: ["id", "handle", "description"],
|
entity: "product",
|
||||||
pagination: {
|
fields: ["id", "handle", "description"],
|
||||||
skip: 5,
|
pagination: {
|
||||||
take: 10,
|
skip: 5,
|
||||||
},
|
take: 10,
|
||||||
filters: QueryFilter<"product">({
|
|
||||||
handle: {
|
|
||||||
$ilike: "abc%",
|
|
||||||
},
|
},
|
||||||
}),
|
filters: {
|
||||||
})
|
handle: {
|
||||||
|
$ilike: "abc%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
expect(format).toEqual({
|
expect(format).toEqual({
|
||||||
product: {
|
product: {
|
||||||
@@ -81,43 +99,48 @@ describe("toRemoteQuery", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should transform a query with filters and context into remote query input [1]", () => {
|
it("should transform a query with filters and context into remote query input [1]", () => {
|
||||||
const format = toRemoteQuery({
|
const format = toRemoteQuery(
|
||||||
entity: "product",
|
{
|
||||||
fields: [
|
entity: "product",
|
||||||
"id",
|
fields: [
|
||||||
"description",
|
"id",
|
||||||
"variants.title",
|
"description",
|
||||||
"variants.calculated_price",
|
"variants.title",
|
||||||
"variants.options.*",
|
"variants.calculated_price",
|
||||||
],
|
"variants.options.*",
|
||||||
filters: {
|
],
|
||||||
variants: QueryFilter<"variants">({
|
filters: {
|
||||||
sku: {
|
variants: {
|
||||||
$ilike: "abc%",
|
sku: {
|
||||||
|
$ilike: "abc%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
variants: {
|
||||||
|
calculated_price: QueryContext({
|
||||||
|
region_id: "reg_123",
|
||||||
|
currency_code: "usd",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
variants: {
|
|
||||||
calculated_price: QueryContext({
|
|
||||||
region_id: "reg_123",
|
|
||||||
currency_code: "usd",
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
expect(format).toEqual({
|
expect(format).toEqual({
|
||||||
product: {
|
product: {
|
||||||
__fields: ["id", "description"],
|
__fields: ["id", "description"],
|
||||||
variants: {
|
__args: {
|
||||||
__args: {
|
filters: {
|
||||||
filters: {
|
variants: {
|
||||||
sku: {
|
sku: {
|
||||||
$ilike: "abc%",
|
$ilike: "abc%",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
calculated_price: {
|
calculated_price: {
|
||||||
__args: {
|
__args: {
|
||||||
context: {
|
context: {
|
||||||
@@ -142,31 +165,34 @@ describe("toRemoteQuery", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const format = toRemoteQuery({
|
const format = toRemoteQuery(
|
||||||
entity: "product",
|
{
|
||||||
fields: [
|
entity: "product",
|
||||||
"id",
|
fields: [
|
||||||
"title",
|
"id",
|
||||||
"description",
|
"title",
|
||||||
"product_translation.*",
|
"description",
|
||||||
"categories.*",
|
"product_translation.*",
|
||||||
"categories.category_translation.*",
|
"categories.*",
|
||||||
"variants.*",
|
"categories.category_translation.*",
|
||||||
"variants.variant_translation.*",
|
"variants.*",
|
||||||
],
|
"variants.variant_translation.*",
|
||||||
filters: QueryFilter<"product">({
|
],
|
||||||
id: "prod_01J742X0QPFW3R2ZFRTRC34FS8",
|
filters: {
|
||||||
}),
|
id: "prod_01J742X0QPFW3R2ZFRTRC34FS8",
|
||||||
context: {
|
|
||||||
product_translation: langContext,
|
|
||||||
categories: {
|
|
||||||
category_translation: langContext,
|
|
||||||
},
|
},
|
||||||
variants: {
|
context: {
|
||||||
variant_translation: langContext,
|
product_translation: langContext,
|
||||||
|
categories: {
|
||||||
|
category_translation: langContext,
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
variant_translation: langContext,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
expect(format).toEqual({
|
expect(format).toEqual({
|
||||||
product: {
|
product: {
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
import {
|
||||||
|
JoinerServiceConfig,
|
||||||
|
JoinerServiceConfigAlias,
|
||||||
|
ModuleJoinerConfig,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { isObject, isString } from "@medusajs/utils"
|
||||||
|
import { MedusaModule } from "../medusa-module"
|
||||||
|
|
||||||
|
const joinerConfigMapCache = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and assign filters to remote query object to the corresponding relation level
|
||||||
|
* @param entryPoint
|
||||||
|
* @param filters
|
||||||
|
* @param remoteQueryObject
|
||||||
|
* @param isFieldAliasNestedRelation
|
||||||
|
* @param entitiesMap
|
||||||
|
*/
|
||||||
|
export function parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
entryPoint,
|
||||||
|
filters,
|
||||||
|
remoteQueryObject,
|
||||||
|
isFieldAliasNestedRelation,
|
||||||
|
}: {
|
||||||
|
remoteQueryObject: object
|
||||||
|
entryPoint: string
|
||||||
|
filters: object
|
||||||
|
isFieldAliasNestedRelation?: boolean
|
||||||
|
},
|
||||||
|
entitiesMap: Map<string, any>
|
||||||
|
) {
|
||||||
|
const joinerConfigs = MedusaModule.getAllJoinerConfigs()
|
||||||
|
|
||||||
|
for (const [filterKey, filterValue] of Object.entries(filters)) {
|
||||||
|
let entryAlias!: JoinerServiceConfigAlias
|
||||||
|
let entryJoinerConfig!: JoinerServiceConfig
|
||||||
|
|
||||||
|
const { joinerConfig, alias } = retrieveJoinerConfigFromPropertyName({
|
||||||
|
entryPoint: entryPoint,
|
||||||
|
joinerConfigs,
|
||||||
|
})
|
||||||
|
|
||||||
|
entryAlias = alias
|
||||||
|
entryJoinerConfig = joinerConfig
|
||||||
|
|
||||||
|
const entryEntity = entitiesMap[entryAlias.entity!]
|
||||||
|
if (!entryEntity) {
|
||||||
|
throw new Error(
|
||||||
|
`Entity ${entryAlias.entity} not found in the public schema of the joiner config from ${entryJoinerConfig.serviceName}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(filterValue)) {
|
||||||
|
for (const [nestedFilterKey, nestedFilterValue] of Object.entries(
|
||||||
|
filterValue
|
||||||
|
)) {
|
||||||
|
const { joinerConfig: filterKeyJoinerConfig } =
|
||||||
|
retrieveJoinerConfigFromPropertyName({
|
||||||
|
entryPoint: nestedFilterKey,
|
||||||
|
joinerConfigs,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
!filterKeyJoinerConfig ||
|
||||||
|
filterKeyJoinerConfig.serviceName === entryJoinerConfig.serviceName
|
||||||
|
) {
|
||||||
|
assignNestedRemoteQueryObject({
|
||||||
|
entryPoint,
|
||||||
|
filterKey,
|
||||||
|
nestedFilterKey,
|
||||||
|
filterValue,
|
||||||
|
nestedFilterValue,
|
||||||
|
remoteQueryObject,
|
||||||
|
isFieldAliasNestedRelation,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const isFieldAliasNestedRelation_ = isFieldAliasNestedRelationHelper({
|
||||||
|
nestedFilterKey,
|
||||||
|
entryJoinerConfig,
|
||||||
|
joinerConfigs,
|
||||||
|
filterKeyJoinerConfig,
|
||||||
|
})
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
entryPoint: nestedFilterKey,
|
||||||
|
filters: nestedFilterValue,
|
||||||
|
remoteQueryObject: remoteQueryObject[entryPoint][filterKey],
|
||||||
|
isFieldAliasNestedRelation: isFieldAliasNestedRelation_,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
assignRemoteQueryObject({
|
||||||
|
entryPoint,
|
||||||
|
filterKey,
|
||||||
|
filterValue,
|
||||||
|
remoteQueryObject,
|
||||||
|
isFieldAliasNestedRelation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function retrieveJoinerConfigFromPropertyName({ entryPoint, joinerConfigs }) {
|
||||||
|
if (joinerConfigMapCache.has(entryPoint)) {
|
||||||
|
return joinerConfigMapCache.get(entryPoint)!
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const joinerConfig of joinerConfigs) {
|
||||||
|
const aliases = joinerConfig.alias
|
||||||
|
const entryPointAlias = aliases.find((alias) => {
|
||||||
|
const aliasNames = Array.isArray(alias.name) ? alias.name : [alias.name]
|
||||||
|
return aliasNames.includes(entryPoint)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (entryPointAlias) {
|
||||||
|
joinerConfigMapCache.set(entryPoint, {
|
||||||
|
joinerConfig,
|
||||||
|
alias: entryPointAlias,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { joinerConfig, alias: entryPointAlias }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignRemoteQueryObject({
|
||||||
|
entryPoint,
|
||||||
|
filterKey,
|
||||||
|
filterValue,
|
||||||
|
remoteQueryObject,
|
||||||
|
isFieldAliasNestedRelation,
|
||||||
|
}: {
|
||||||
|
entryPoint: string
|
||||||
|
filterKey: string
|
||||||
|
filterValue: any
|
||||||
|
remoteQueryObject: object
|
||||||
|
isFieldAliasNestedRelation?: boolean
|
||||||
|
}) {
|
||||||
|
remoteQueryObject[entryPoint] ??= {}
|
||||||
|
remoteQueryObject[entryPoint].__args ??= {}
|
||||||
|
remoteQueryObject[entryPoint].__args["filters"] ??= {}
|
||||||
|
|
||||||
|
if (!isFieldAliasNestedRelation) {
|
||||||
|
remoteQueryObject[entryPoint].__args["filters"][filterKey] = filterValue
|
||||||
|
} else {
|
||||||
|
// In case of field alias that refers to a relation of linked entity we need to assign the filter on the relation filter itself instead of top level of the args\
|
||||||
|
remoteQueryObject[entryPoint].__args["filters"][entryPoint] ??= {}
|
||||||
|
remoteQueryObject[entryPoint].__args["filters"][entryPoint][filterKey] =
|
||||||
|
filterValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignNestedRemoteQueryObject({
|
||||||
|
entryPoint,
|
||||||
|
filterKey,
|
||||||
|
nestedFilterKey,
|
||||||
|
nestedFilterValue,
|
||||||
|
remoteQueryObject,
|
||||||
|
isFieldAliasNestedRelation,
|
||||||
|
}: {
|
||||||
|
entryPoint: string
|
||||||
|
filterKey: string
|
||||||
|
filterValue: any
|
||||||
|
nestedFilterKey: string
|
||||||
|
nestedFilterValue: any
|
||||||
|
remoteQueryObject: object
|
||||||
|
isFieldAliasNestedRelation?: boolean
|
||||||
|
}) {
|
||||||
|
remoteQueryObject[entryPoint] ??= {}
|
||||||
|
remoteQueryObject[entryPoint]["__args"] ??= {}
|
||||||
|
remoteQueryObject[entryPoint]["__args"]["filters"] ??= {}
|
||||||
|
|
||||||
|
if (!isFieldAliasNestedRelation) {
|
||||||
|
remoteQueryObject[entryPoint]["__args"]["filters"][filterKey] ??= {}
|
||||||
|
remoteQueryObject[entryPoint]["__args"]["filters"][filterKey][
|
||||||
|
nestedFilterKey
|
||||||
|
] = nestedFilterValue
|
||||||
|
} else {
|
||||||
|
// In case of field alias that refers to a relation of linked entity we need to assign the filter on the relation filter itself instead of top level of the args
|
||||||
|
remoteQueryObject[entryPoint]["__args"]["filters"][entryPoint] ??= {}
|
||||||
|
remoteQueryObject[entryPoint]["__args"]["filters"][entryPoint][
|
||||||
|
filterKey
|
||||||
|
] ??= {}
|
||||||
|
remoteQueryObject[entryPoint]["__args"]["filters"][entryPoint][filterKey][
|
||||||
|
nestedFilterKey
|
||||||
|
] = nestedFilterValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFieldAliasNestedRelationHelper({
|
||||||
|
nestedFilterKey,
|
||||||
|
entryJoinerConfig,
|
||||||
|
joinerConfigs,
|
||||||
|
filterKeyJoinerConfig,
|
||||||
|
}: {
|
||||||
|
nestedFilterKey: string
|
||||||
|
entryJoinerConfig: ModuleJoinerConfig
|
||||||
|
joinerConfigs: ModuleJoinerConfig[]
|
||||||
|
filterKeyJoinerConfig: ModuleJoinerConfig
|
||||||
|
}): boolean {
|
||||||
|
const linkJoinerConfig = joinerConfigs.find((joinerConfig) => {
|
||||||
|
return joinerConfig.relationships?.every(
|
||||||
|
(rel) =>
|
||||||
|
rel.serviceName === entryJoinerConfig.serviceName ||
|
||||||
|
rel.serviceName === filterKeyJoinerConfig.serviceName
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const relationsAlias = linkJoinerConfig?.relationships?.map((r) => r.alias)
|
||||||
|
|
||||||
|
let isFieldAliasNestedRelation = false
|
||||||
|
|
||||||
|
if (linkJoinerConfig && relationsAlias?.length) {
|
||||||
|
const fieldAlias = linkJoinerConfig.extends?.find(
|
||||||
|
(extend) => extend.fieldAlias?.[nestedFilterKey]
|
||||||
|
)?.fieldAlias
|
||||||
|
|
||||||
|
if (fieldAlias) {
|
||||||
|
const path = isString(fieldAlias?.[nestedFilterKey])
|
||||||
|
? fieldAlias?.[nestedFilterKey]
|
||||||
|
: (fieldAlias?.[nestedFilterKey] as any).path
|
||||||
|
|
||||||
|
if (!relationsAlias.includes(path.split(".").pop())) {
|
||||||
|
isFieldAliasNestedRelation = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isFieldAliasNestedRelation
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ import {
|
|||||||
RemoteQueryObjectFromStringResult,
|
RemoteQueryObjectFromStringResult,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
isObject,
|
|
||||||
MedusaError,
|
MedusaError,
|
||||||
|
isObject,
|
||||||
remoteQueryObjectFromString,
|
remoteQueryObjectFromString,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { RemoteQuery } from "./remote-query"
|
import { RemoteQuery } from "./remote-query"
|
||||||
@@ -69,7 +69,10 @@ export class Query {
|
|||||||
if ("__value" in config) {
|
if ("__value" in config) {
|
||||||
normalizedQuery = config.__value
|
normalizedQuery = config.__value
|
||||||
} else if ("entity" in normalizedQuery) {
|
} else if ("entity" in normalizedQuery) {
|
||||||
normalizedQuery = toRemoteQuery(normalizedQuery)
|
normalizedQuery = toRemoteQuery(
|
||||||
|
normalizedQuery,
|
||||||
|
this.#remoteQuery.getEntitiesMap()
|
||||||
|
)
|
||||||
} else if (
|
} else if (
|
||||||
"entryPoint" in normalizedQuery ||
|
"entryPoint" in normalizedQuery ||
|
||||||
"service" in normalizedQuery
|
"service" in normalizedQuery
|
||||||
@@ -141,7 +144,10 @@ export class Query {
|
|||||||
queryOptions: RemoteQueryInput<TEntry>,
|
queryOptions: RemoteQueryInput<TEntry>,
|
||||||
options?: RemoteJoinerOptions
|
options?: RemoteJoinerOptions
|
||||||
): Promise<GraphResultSet<TEntry>> {
|
): Promise<GraphResultSet<TEntry>> {
|
||||||
const normalizedQuery = toRemoteQuery<TEntry>(queryOptions)
|
const normalizedQuery = toRemoteQuery<TEntry>(
|
||||||
|
queryOptions,
|
||||||
|
this.#remoteQuery.getEntitiesMap()
|
||||||
|
)
|
||||||
let response:
|
let response:
|
||||||
| any[]
|
| any[]
|
||||||
| { rows: any[]; metadata: RemoteQueryFunctionReturnPagination }
|
| { rows: any[]; metadata: RemoteQueryFunctionReturnPagination }
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export class RemoteQuery {
|
|||||||
private remoteJoiner: RemoteJoiner
|
private remoteJoiner: RemoteJoiner
|
||||||
private modulesMap: Map<string, LoadedModule> = new Map()
|
private modulesMap: Map<string, LoadedModule> = new Map()
|
||||||
private customRemoteFetchData?: RemoteFetchDataCallback
|
private customRemoteFetchData?: RemoteFetchDataCallback
|
||||||
|
private entitiesMap: Map<string, any> = new Map()
|
||||||
|
|
||||||
static traceFetchRemoteData?: (
|
static traceFetchRemoteData?: (
|
||||||
fetcher: () => Promise<any>,
|
fetcher: () => Promise<any>,
|
||||||
@@ -33,12 +34,15 @@ export class RemoteQuery {
|
|||||||
modulesLoaded,
|
modulesLoaded,
|
||||||
customRemoteFetchData,
|
customRemoteFetchData,
|
||||||
servicesConfig = [],
|
servicesConfig = [],
|
||||||
|
entitiesMap,
|
||||||
}: {
|
}: {
|
||||||
modulesLoaded?: LoadedModule[]
|
modulesLoaded?: LoadedModule[]
|
||||||
customRemoteFetchData?: RemoteFetchDataCallback
|
customRemoteFetchData?: RemoteFetchDataCallback
|
||||||
servicesConfig?: ModuleJoinerConfig[]
|
servicesConfig?: ModuleJoinerConfig[]
|
||||||
|
entitiesMap: Map<string, any>
|
||||||
}) {
|
}) {
|
||||||
const servicesConfig_ = [...servicesConfig]
|
const servicesConfig_ = [...servicesConfig]
|
||||||
|
this.entitiesMap = entitiesMap
|
||||||
|
|
||||||
if (!modulesLoaded?.length) {
|
if (!modulesLoaded?.length) {
|
||||||
modulesLoaded = MedusaModule.getLoadedModules().map(
|
modulesLoaded = MedusaModule.getLoadedModules().map(
|
||||||
@@ -72,6 +76,10 @@ export class RemoteQuery {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getEntitiesMap() {
|
||||||
|
return this.entitiesMap
|
||||||
|
}
|
||||||
|
|
||||||
public setFetchDataCallback(
|
public setFetchDataCallback(
|
||||||
remoteFetchData: (
|
remoteFetchData: (
|
||||||
expand: RemoteExpandProperty,
|
expand: RemoteExpandProperty,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
RemoteQueryObjectConfig,
|
RemoteQueryObjectConfig,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import { QueryContext, QueryFilter, isObject } from "@medusajs/utils"
|
import { QueryContext, QueryFilter, isObject } from "@medusajs/utils"
|
||||||
|
import { parseAndAssignFilters } from "./parse-filters"
|
||||||
|
|
||||||
const FIELDS = "__fields"
|
const FIELDS = "__fields"
|
||||||
const ARGUMENTS = "__args"
|
const ARGUMENTS = "__args"
|
||||||
@@ -27,16 +28,19 @@ const ARGUMENTS = "__args"
|
|||||||
* console.log(remoteQueryObject);
|
* console.log(remoteQueryObject);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function toRemoteQuery<const TEntity extends string>(config: {
|
export function toRemoteQuery<const TEntity extends string>(
|
||||||
entity: TEntity | keyof RemoteQueryEntryPoints
|
config: {
|
||||||
fields: RemoteQueryObjectConfig<TEntity>["fields"]
|
entity: TEntity | keyof RemoteQueryEntryPoints
|
||||||
filters?: RemoteQueryFilters<TEntity>
|
fields: RemoteQueryObjectConfig<TEntity>["fields"]
|
||||||
pagination?: {
|
filters?: RemoteQueryFilters<TEntity>
|
||||||
skip?: number
|
pagination?: {
|
||||||
take?: number
|
skip?: number
|
||||||
}
|
take?: number
|
||||||
context?: Record<string, any>
|
}
|
||||||
}): RemoteQueryGraph<TEntity> {
|
context?: Record<string, any>
|
||||||
|
},
|
||||||
|
entitiesMap: Map<string, any>
|
||||||
|
): RemoteQueryGraph<TEntity> {
|
||||||
const { entity, fields = [], filters = {}, context = {} } = config
|
const { entity, fields = [], filters = {}, context = {} } = config
|
||||||
|
|
||||||
const joinerQuery: Record<string, any> = {
|
const joinerQuery: Record<string, any> = {
|
||||||
@@ -84,7 +88,6 @@ export function toRemoteQuery<const TEntity extends string>(config: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process filters and context recursively
|
// Process filters and context recursively
|
||||||
processNestedObjects(joinerQuery[entity], filters)
|
|
||||||
processNestedObjects(joinerQuery[entity], context)
|
processNestedObjects(joinerQuery[entity], context)
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
@@ -116,5 +119,14 @@ export function toRemoteQuery<const TEntity extends string>(config: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parseAndAssignFilters(
|
||||||
|
{
|
||||||
|
entryPoint: entity,
|
||||||
|
filters: filters,
|
||||||
|
remoteQueryObject: joinerQuery,
|
||||||
|
},
|
||||||
|
entitiesMap
|
||||||
|
)
|
||||||
|
|
||||||
return joinerQuery as RemoteQueryGraph<TEntity>
|
return joinerQuery as RemoteQueryGraph<TEntity>
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,7 @@ export const ProductVariantPriceSet: ModuleJoinerConfig = {
|
|||||||
prices: {
|
prices: {
|
||||||
path: "price_set_link.price_set.prices",
|
path: "price_set_link.price_set.prices",
|
||||||
isList: true,
|
isList: true,
|
||||||
|
forwardArgumentsOnPath: ["price_set_link.price_set"],
|
||||||
},
|
},
|
||||||
calculated_price: {
|
calculated_price: {
|
||||||
path: "price_set_link.price_set.calculated_price",
|
path: "price_set_link.price_set.calculated_price",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
export const schema = `
|
export const schema = `
|
||||||
type PriceSet {
|
type PriceSet {
|
||||||
id: ID!
|
id: ID!
|
||||||
prices: [MoneyAmount]
|
prices: [Price]
|
||||||
calculated_price: CalculatedPriceSet
|
calculated_price: CalculatedPriceSet
|
||||||
}
|
}
|
||||||
|
|
||||||
type MoneyAmount {
|
type Price {
|
||||||
id: ID!
|
id: ID!
|
||||||
currency_code: String
|
currency_code: String
|
||||||
amount: Float
|
amount: Float
|
||||||
|
|||||||
Reference in new issue
Block a user