UNPKG

@medusajs/index

Version:
829 lines • 41.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CustomDirectives = void 0; exports.makeSchemaExecutable = makeSchemaExecutable; exports.buildSchemaObjectRepresentation = buildSchemaObjectRepresentation; const modules_sdk_1 = require("@medusajs/framework/modules-sdk"); const utils_1 = require("@medusajs/framework/utils"); const _types_1 = require("../types"); const base_graphql_schema_1 = require("./base-graphql-schema"); exports.CustomDirectives = { Listeners: { configurationPropertyName: "listeners", isRequired: true, name: "Listeners", directive: "@Listeners", definition: "directive @Listeners (values: [String!]) on OBJECT", }, }; function makeSchemaExecutable(inputSchema) { const { schema: cleanedSchema } = utils_1.GraphQLUtils.cleanGraphQLSchema(inputSchema); if (!cleanedSchema) { return; } return utils_1.GraphQLUtils.makeExecutableSchema({ typeDefs: cleanedSchema, }); } /** * Retrieve the property name of the source entity that corresponds to the target entity * @param sourceEntityName - The name of the source entity * @param targetEntityName - The name of the target entity * @param entitiesMap - The map of entities configured in the module * @param servicesEntityMap - The map of entities configured in the services * @param node - The node of the source entity * @returns The property name and if it is an array of the source entity property that corresponds to the target entity */ function retrieveEntityPropByType({ sourceEntityName, targetEntityName, entitiesMap, servicesEntityMap, node, }) { if (!node && !entitiesMap && !servicesEntityMap) { throw new Error("Index Module error, unable to retrieve the entity property by type. Please provide either the entitiesMap and servicesEntityMap or the node."); } const retrieveFieldNode = (node) => { const astNode = node?.astNode; const fields = astNode?.fields ?? []; for (const field of fields) { let type = field.type; let isArray = false; while (type.type) { if (type.kind === utils_1.GraphQLUtils.Kind.LIST_TYPE) { isArray = true; } type = type.type; } if (type.name?.value === targetEntityName) { return { name: field.name?.value, isArray }; } } return; }; let prop; if (node) { prop = retrieveFieldNode(node); } if (entitiesMap && !prop) { prop = retrieveFieldNode(entitiesMap[sourceEntityName]); } if (servicesEntityMap && !prop) { prop = retrieveFieldNode(servicesEntityMap[sourceEntityName]); } return (prop && { name: prop?.name, isArray: prop?.isArray, }); } function extractNameFromAlias(alias) { const alias_ = Array.isArray(alias) ? alias[0] : alias; const names = Array.isArray(alias_?.name) ? alias_?.name : [alias_?.name]; return names[0]; } function retrieveAliasForEntity(entityName, aliases) { aliases = aliases ? (Array.isArray(aliases) ? aliases : [aliases]) : []; for (const alias of aliases) { const names = Array.isArray(alias.name) ? alias.name : [alias.name]; if (alias.entity === entityName) { return names[0]; } for (const name of names) { if (name.toLowerCase() === entityName.toLowerCase()) { return name; } } } } function retrieveModuleAndAlias(entityName, moduleJoinerConfigs) { let relatedModule; let alias; for (const moduleJoinerConfig of moduleJoinerConfigs) { const moduleSchema = moduleJoinerConfig.schema; const moduleAliases = moduleJoinerConfig.alias; /** * If the entity exist in the module schema, then the current module is the * one we are looking for. * * If the module does not have any schema, then we need to base the search * on the provided aliases. in any case, we try to get both */ if (moduleSchema) { const executableSchema = makeSchemaExecutable(moduleSchema); const entitiesMap = executableSchema?.getTypeMap(); if (entitiesMap?.[entityName]) { relatedModule = moduleJoinerConfig; } } if (relatedModule && moduleAliases) { alias = retrieveAliasForEntity(entityName, moduleJoinerConfig.alias); } if (relatedModule) { break; } } if (!relatedModule) { return { relatedModule: null, alias: null }; } if (!alias) { throw new Error(`Index Module error, the module ${relatedModule?.serviceName} has a schema but does not have any alias for the entity ${entityName}. Please add an alias to the module configuration and the entity it correspond to in the args under the entity property.`); } return { relatedModule, alias }; } // TODO: rename util function retrieveLinkModuleAndAlias({ primaryEntity, primaryModuleConfig, foreignEntity, foreignModuleConfig, moduleJoinerConfigs, servicesEntityMap, entitiesMap, }) { const linkModulesMetadata = []; for (const linkModuleJoinerConfig of moduleJoinerConfigs.filter((config) => config.isLink && !config.isReadOnlyLink)) { const linkPrimary = linkModuleJoinerConfig.relationships[0]; const linkForeign = linkModuleJoinerConfig.relationships[1]; const isDirectMatch = linkPrimary.serviceName === primaryModuleConfig.serviceName && linkForeign.serviceName === foreignModuleConfig.serviceName && linkPrimary.entity === primaryEntity; const isInverseMatch = linkPrimary.serviceName === foreignModuleConfig.serviceName && linkForeign.serviceName === primaryModuleConfig.serviceName && linkPrimary.entity === foreignEntity; if (!(isDirectMatch || isInverseMatch)) { continue; } const primaryEntityLinkableKey = isDirectMatch ? linkPrimary.foreignKey : linkForeign.foreignKey; const isTheForeignKeyEntityEqualPrimaryEntity = primaryModuleConfig.linkableKeys?.[primaryEntityLinkableKey] === primaryEntity; const foreignEntityLinkableKey = isDirectMatch ? linkForeign.foreignKey : linkPrimary.foreignKey; const isTheForeignKeyEntityEqualForeignEntity = foreignModuleConfig.linkableKeys?.[foreignEntityLinkableKey] === foreignEntity; const relationshipsTypes = linkModuleJoinerConfig.relationships ?.map((relationship) => relationship.entity) .filter(Boolean) ?? []; const filteredEntitiesMap = { [linkModuleJoinerConfig.alias?.[0].entity]: entitiesMap[linkModuleJoinerConfig.alias?.[0].entity], }; const filteredServicesEntityMap = { [linkModuleJoinerConfig.alias?.[0].entity]: servicesEntityMap[linkModuleJoinerConfig.alias?.[0].entity], }; for (const relationshipType of relationshipsTypes) { filteredEntitiesMap[relationshipType] = entitiesMap[relationshipType]; filteredServicesEntityMap[relationshipType] = servicesEntityMap[relationshipType]; } const linkName = linkModuleJoinerConfig.extends?.find((extend) => { return ((extend.serviceName === primaryModuleConfig.serviceName || extend.relationship.serviceName === foreignModuleConfig.serviceName) && (extend.relationship.primaryKey === primaryEntityLinkableKey || extend.relationship.primaryKey === foreignEntityLinkableKey)); })?.relationship.serviceName; if (!linkName) { throw new Error(`Index Module error, unable to retrieve the link module name for the services ${primaryModuleConfig.serviceName} - ${foreignModuleConfig.serviceName}. Please be sure that the extend relationship service name is set correctly`); } if (!linkModuleJoinerConfig.alias?.[0]?.entity) { throw new Error(`Index Module error, unable to retrieve the link module entity name for the services ${primaryModuleConfig.serviceName} - ${foreignModuleConfig.serviceName}. Please be sure that the link module alias has an entity property in the args.`); } if (isTheForeignKeyEntityEqualPrimaryEntity && isTheForeignKeyEntityEqualForeignEntity) { /** * The link will become the parent of the foreign entity, that is why the alias must be the one that correspond to the extended foreign module */ const inverseSideProp = retrieveEntityPropByType({ targetEntityName: primaryEntity, sourceEntityName: linkModuleJoinerConfig.alias[0].entity, servicesEntityMap: filteredServicesEntityMap, entitiesMap: filteredEntitiesMap, }); linkModulesMetadata.push({ entityName: linkModuleJoinerConfig.alias[0].entity, alias: extractNameFromAlias(linkModuleJoinerConfig.alias), linkModuleConfig: linkModuleJoinerConfig, intermediateEntityNames: [], isInverse: isInverseMatch, isList: inverseSideProp?.isArray, inverseSideProp: inverseSideProp?.name, }); } else { const intermediateEntityName = foreignModuleConfig.linkableKeys[foreignEntityLinkableKey]; const moduleSchema = isDirectMatch ? foreignModuleConfig.schema : primaryModuleConfig.schema; if (!moduleSchema) { throw new Error(`Index Module error, unable to retrieve the intermediate entity name for the services ${primaryModuleConfig.serviceName} - ${foreignModuleConfig.serviceName}. Please be sure that the foreign module ${foreignModuleConfig.serviceName} has a schema.`); } const entitiesMap = servicesEntityMap; let intermediateEntities = []; let foundCount = 0; let foundName = null; const isForeignEntityChildOfIntermediateEntity = (entityName, visited = new Set()) => { if (visited.has(entityName)) { return false; } visited.add(entityName); for (const entityType of Object.values(entitiesMap)) { const inverseSideProp = retrieveEntityPropByType({ node: entityType, targetEntityName: entityName, }); if (entityType.astNode?.kind === "ObjectTypeDefinition" && inverseSideProp) { if (entityType.name === intermediateEntityName) { foundName = entityType.name; ++foundCount; return true; } else { const inverseSideProp = isForeignEntityChildOfIntermediateEntity(entityType.name, visited); if (inverseSideProp) { intermediateEntities.push(entityType.name); return true; } } } } return false; }; isForeignEntityChildOfIntermediateEntity(isDirectMatch ? foreignEntity : primaryEntity); if (foundCount !== 1) { throw new Error(`Index Module error, unable to retrieve the intermediate entities for the services ${primaryModuleConfig.serviceName} - ${foreignModuleConfig.serviceName} between ${isDirectMatch ? foreignEntity : primaryEntity} and ${intermediateEntityName}. Multiple paths or no path found. Please check your schema in ${foreignModuleConfig.serviceName}`); } intermediateEntities.push(foundName); /** * The link will become the parent of the foreign entity, that is why the alias must be the one that correspond to the extended foreign module */ const directInverseSideProp = retrieveEntityPropByType({ targetEntityName: isDirectMatch ? primaryEntity : linkModuleJoinerConfig.alias[0].entity, sourceEntityName: isDirectMatch ? linkModuleJoinerConfig.alias[0].entity : primaryEntity, servicesEntityMap: filteredServicesEntityMap, entitiesMap: filteredEntitiesMap, }); linkModulesMetadata.push({ entityName: linkModuleJoinerConfig.alias[0].entity, alias: extractNameFromAlias(linkModuleJoinerConfig.alias), linkModuleConfig: linkModuleJoinerConfig, intermediateEntityNames: intermediateEntities, inverseSideProp: directInverseSideProp?.name, isList: directInverseSideProp?.isArray, isInverse: isInverseMatch, }); } } if (!linkModulesMetadata.length) { console.warn(`Index Module warning, unable to retrieve the link module that correspond to the entities ${primaryEntity} - ${foreignEntity}.`); } return linkModulesMetadata; } function getObjectRepresentationRef(entityName, { objectRepresentationRef }) { return (objectRepresentationRef[entityName] ??= { entity: entityName, parents: [], alias: "", listeners: [], moduleConfig: null, fields: [], }); } function setCustomDirectives(currentObjectRepresentationRef, directives) { for (const customDirectiveConfiguration of Object.values(exports.CustomDirectives)) { const directive = directives.find((typeDirective) => typeDirective.name.value === customDirectiveConfiguration.name); if (!directive) { return; } // Only support array directive value for now currentObjectRepresentationRef[customDirectiveConfiguration.configurationPropertyName] = (directive.arguments[0].value?.values ?? []).map((v) => v.value); } } function processEntity(entityName, { entitiesMap, servicesEntityMap, moduleJoinerConfigs, objectRepresentationRef, }) { /** * Get the reference to the object representation for the current entity. */ const currentObjectRepresentationRef = getObjectRepresentationRef(entityName, { objectRepresentationRef, }); /** * Retrieve and set the custom directives for the current entity. */ setCustomDirectives(currentObjectRepresentationRef, entitiesMap[entityName].astNode?.directives ?? []); // Merge and deduplicate the fields currentObjectRepresentationRef.fields ??= []; currentObjectRepresentationRef.fields = currentObjectRepresentationRef.fields.concat(...(utils_1.GraphQLUtils.gqlGetFieldsAndRelations(entitiesMap, entityName) ?? [])); currentObjectRepresentationRef.fields = Array.from(new Set(currentObjectRepresentationRef.fields)); /** * Retrieve the module and alias for the current entity. */ const { relatedModule: currentEntityModule, alias } = retrieveModuleAndAlias(entityName, moduleJoinerConfigs); if (!currentEntityModule && currentObjectRepresentationRef.listeners.length > 0) { const example = JSON.stringify({ alias: [ { name: "entity-alias", entity: entityName, }, ], }); throw new Error(`Index Module error, unable to retrieve the module that corresponds to the entity ${entityName}.\nPlease add the entity to the module schema or add an alias to the joiner config like the example below:\n${example}`); } if (currentEntityModule) { objectRepresentationRef._serviceNameModuleConfigMap[currentEntityModule.serviceName] = currentEntityModule; currentObjectRepresentationRef.moduleConfig = currentEntityModule; currentObjectRepresentationRef.alias = alias; } /** * Retrieve the parent entities for the current entity. */ const schemaParentEntity = Object.values(entitiesMap).filter((value) => { return (value.astNode && value.astNode.fields?.some((field) => { let currentType = field.type; while (currentType.type) { currentType = currentType.type; } return currentType.name?.value === entityName; })); }); if (!schemaParentEntity.length) { return; } /** * If the current entity has parent entities, then we need to process them. */ const parentEntityNames = schemaParentEntity.map((parent) => { return parent.name; }); for (const parent of parentEntityNames) { /** * Retrieve the parent entity field in the schema */ const entityFieldInParent = retrieveEntityPropByType({ sourceEntityName: parent, targetEntityName: entityName, entitiesMap, servicesEntityMap, }); const entityTargetPropertyNameInParent = entityFieldInParent.name; const entityTargetPropertyIsListInParent = entityFieldInParent.isArray; /** * Retrieve the parent entity object representation reference. */ if (!objectRepresentationRef[parent]) { processEntity(parent, { entitiesMap, servicesEntityMap, moduleJoinerConfigs, objectRepresentationRef, }); } const parentObjectRepresentationRef = getObjectRepresentationRef(parent, { objectRepresentationRef, }); const parentModuleConfig = parentObjectRepresentationRef.moduleConfig; // If the entity is not part of any module, just set the parent and continue if (!currentObjectRepresentationRef.moduleConfig) { const parentAlreadyExists = currentObjectRepresentationRef.parents.some((existingParent) => existingParent.ref?.entity === parentObjectRepresentationRef.entity && existingParent.targetProp === entityTargetPropertyNameInParent); const parentPropertyNameWithinCurrentEntity = retrieveEntityPropByType({ sourceEntityName: entityName, targetEntityName: parent, entitiesMap, servicesEntityMap, }); if (!parentAlreadyExists) { currentObjectRepresentationRef.parents.push({ ref: parentObjectRepresentationRef, targetProp: entityTargetPropertyNameInParent, inverseSideProp: parentPropertyNameWithinCurrentEntity?.name, isList: entityTargetPropertyIsListInParent, }); } else { return; } continue; } /** * If the parent entity and the current entity are part of the same service then configure the parent and * add the parent id as a field to the current entity. */ if (currentObjectRepresentationRef.moduleConfig.serviceName === parentModuleConfig.serviceName || parentModuleConfig.isLink || currentObjectRepresentationRef.moduleConfig.isLink) { const parentPropertyNameWithinCurrentEntity = retrieveEntityPropByType({ sourceEntityName: entityName, targetEntityName: parent, entitiesMap, servicesEntityMap, }); const parentAlreadyExists = currentObjectRepresentationRef.parents.some((existingParent) => existingParent.ref?.entity === parentObjectRepresentationRef.entity && existingParent.targetProp === entityTargetPropertyNameInParent); if (!parentAlreadyExists) { currentObjectRepresentationRef.parents.push({ ref: parentObjectRepresentationRef, targetProp: entityTargetPropertyNameInParent, inverseSideProp: parentPropertyNameWithinCurrentEntity?.name, isList: entityTargetPropertyIsListInParent, }); } const propertyToAdd = parentPropertyNameWithinCurrentEntity?.name + ".id"; if (parentPropertyNameWithinCurrentEntity && !currentObjectRepresentationRef.fields.includes(propertyToAdd)) { currentObjectRepresentationRef.fields.push(propertyToAdd); } if (parentAlreadyExists) { return; } } else { /** * If the parent entity and the current entity are not part of the same service then we need to * find the link module that join them. */ const linkModuleMetadatas = retrieveLinkModuleAndAlias({ primaryEntity: parentObjectRepresentationRef.entity, primaryModuleConfig: parentModuleConfig, foreignEntity: currentObjectRepresentationRef.entity, foreignModuleConfig: currentEntityModule, moduleJoinerConfigs, servicesEntityMap, entitiesMap, }); for (const linkModuleMetadata of linkModuleMetadatas) { const linkObjectRepresentationRef = getObjectRepresentationRef(linkModuleMetadata.entityName, { objectRepresentationRef }); objectRepresentationRef._serviceNameModuleConfigMap[linkModuleMetadata.linkModuleConfig.serviceName || linkModuleMetadata.entityName] = linkModuleMetadata.linkModuleConfig; /** * Add the schema parent entity as a parent to the link module and configure it. */ linkObjectRepresentationRef.parents ??= []; if (!linkObjectRepresentationRef.parents.some((parent) => parent.ref.entity === parentObjectRepresentationRef.entity)) { linkObjectRepresentationRef.parents.push({ ref: parentObjectRepresentationRef, targetProp: linkModuleMetadata.alias, inverseSideProp: linkModuleMetadata.inverseSideProp ?? "", isList: linkModuleMetadata.isList, isInverse: linkModuleMetadata.isInverse, }); } linkObjectRepresentationRef.alias = linkModuleMetadata.alias; linkObjectRepresentationRef.listeners = [ `${linkModuleMetadata.entityName}.${utils_1.CommonEvents.ATTACHED}`, `${linkModuleMetadata.entityName}.${utils_1.CommonEvents.DETACHED}`, ]; linkObjectRepresentationRef.moduleConfig = linkModuleMetadata.linkModuleConfig; linkObjectRepresentationRef.fields = [ "id", ...linkModuleMetadata.linkModuleConfig .relationships.map((relationship) => [ parentModuleConfig.serviceName, currentEntityModule.serviceName, ].includes(relationship.serviceName) && relationship.foreignKey) .filter((v) => Boolean(v)), ]; /** * If the current entity is not the entity that is used to join the link module and the parent entity * then we need to add the new entity that join them and then add the link as its parent * before setting the new entity as the true parent of the current entity. */ for (let i = linkModuleMetadata.intermediateEntityNames.length - 1; i >= 0; --i) { const intermediateEntityName = linkModuleMetadata.intermediateEntityNames[i]; const isLastIntermediateEntity = i === linkModuleMetadata.intermediateEntityNames.length - 1; const parentIntermediateEntityRef = isLastIntermediateEntity ? linkObjectRepresentationRef : objectRepresentationRef[linkModuleMetadata.intermediateEntityNames[i + 1]]; const { relatedModule: intermediateEntityModule, alias: intermediateEntityAlias, } = retrieveModuleAndAlias(intermediateEntityName, moduleJoinerConfigs); const intermediateEntityObjectRepresentationRef = getObjectRepresentationRef(intermediateEntityName, { objectRepresentationRef, }); objectRepresentationRef._serviceNameModuleConfigMap[intermediateEntityModule.serviceName] = intermediateEntityModule; const parentPropertyNameWithinIntermediateEntity = retrieveEntityPropByType({ sourceEntityName: intermediateEntityName, targetEntityName: parentIntermediateEntityRef.entity, entitiesMap: entitiesMap, servicesEntityMap: servicesEntityMap, }); const intermediateEntityTargetPropertyIsListInParent = retrieveEntityPropByType({ sourceEntityName: parentIntermediateEntityRef.entity, targetEntityName: intermediateEntityName, entitiesMap: entitiesMap, servicesEntityMap: servicesEntityMap, })?.isArray; const parentRef = { ref: parentIntermediateEntityRef, targetProp: intermediateEntityAlias, inverseSideProp: parentPropertyNameWithinIntermediateEntity?.name, isList: intermediateEntityTargetPropertyIsListInParent, }; const parentAlreadyExists = intermediateEntityObjectRepresentationRef.parents.some((existingParent) => existingParent.ref?.entity === parentIntermediateEntityRef.entity && existingParent.targetProp === intermediateEntityAlias); if (!parentAlreadyExists) { intermediateEntityObjectRepresentationRef.parents.push(parentRef); } intermediateEntityObjectRepresentationRef.alias = intermediateEntityAlias; const kebabCasedServiceName = (0, utils_1.lowerCaseFirst)((0, utils_1.kebabCase)(intermediateEntityModule.serviceName)); intermediateEntityObjectRepresentationRef.listeners = [ (0, utils_1.buildModuleResourceEventName)({ action: utils_1.CommonEvents.CREATED, objectName: intermediateEntityName, prefix: kebabCasedServiceName, }), (0, utils_1.buildModuleResourceEventName)({ action: utils_1.CommonEvents.UPDATED, objectName: intermediateEntityName, prefix: kebabCasedServiceName, }), (0, utils_1.buildModuleResourceEventName)({ action: utils_1.CommonEvents.DELETED, objectName: intermediateEntityName, prefix: kebabCasedServiceName, }), ]; intermediateEntityObjectRepresentationRef.moduleConfig = intermediateEntityModule; intermediateEntityObjectRepresentationRef.fields = ["id"]; /** * We push the parent id only between intermediate entities but not between intermediate and link */ if (!isLastIntermediateEntity) { const propertyToAdd = parentPropertyNameWithinIntermediateEntity?.name + ".id"; if (parentPropertyNameWithinIntermediateEntity && !intermediateEntityObjectRepresentationRef.fields.includes(propertyToAdd)) { intermediateEntityObjectRepresentationRef.fields.push(propertyToAdd); } } } /** * If there is any intermediate entity then we need to set the last one as the parent field for the current entity. * otherwise there is not need to set the link id field into the current entity. */ let currentParentIntermediateRef = linkObjectRepresentationRef; if (linkModuleMetadata.intermediateEntityNames.length) { currentParentIntermediateRef = objectRepresentationRef[linkModuleMetadata.intermediateEntityNames[0]]; const parentPropertyNameWithinCurrentEntity = retrieveEntityPropByType({ sourceEntityName: currentObjectRepresentationRef.entity, targetEntityName: currentParentIntermediateRef.entity, entitiesMap: servicesEntityMap, }); const propertyToAdd = parentPropertyNameWithinCurrentEntity?.name + ".id"; if (parentPropertyNameWithinCurrentEntity && !currentObjectRepresentationRef.fields.includes(propertyToAdd)) { currentObjectRepresentationRef.fields.push(propertyToAdd); } } const parentPropertyNameWithinCurrentEntity = retrieveEntityPropByType({ sourceEntityName: currentObjectRepresentationRef.entity, targetEntityName: currentParentIntermediateRef.entity, entitiesMap: servicesEntityMap, }); const entityTargetPropertyIsListInParent = retrieveEntityPropByType({ sourceEntityName: currentParentIntermediateRef.entity, targetEntityName: currentObjectRepresentationRef.entity, entitiesMap: entitiesMap, servicesEntityMap: servicesEntityMap, })?.isArray; const parentAlreadyExists = currentObjectRepresentationRef.parents.some((existingParent) => existingParent.ref?.entity === currentParentIntermediateRef.entity && existingParent.targetProp === entityTargetPropertyNameInParent); if (!parentAlreadyExists) { currentObjectRepresentationRef.parents.push({ ref: currentParentIntermediateRef, inSchemaRef: parentObjectRepresentationRef, targetProp: entityTargetPropertyNameInParent, inverseSideProp: parentPropertyNameWithinCurrentEntity?.name, isList: entityTargetPropertyIsListInParent, }); } } } } } function getServicesEntityMap(moduleJoinerConfigs, addtionalSchema = "") { return makeSchemaExecutable(base_graphql_schema_1.baseGraphqlSchema + "\n" + moduleJoinerConfigs .map((joinerConfig) => joinerConfig?.schema ?? "") .join("\n") + "\n" + addtionalSchema).getTypeMap(); } /** * Build a special object which will be used to retrieve the correct * object representation using path tree * * @example * { * _schemaPropertiesMap: { * "product": <ProductRef> * "product.variants": <ProductVariantRef> * } * } */ function buildAliasMap(objectRepresentation) { const aliasMap = {}; function recursivelyBuildAliasPath(current, parentPath = "", aliases = [], visited = new Set(), pathStack = []) { const pathIdentifier = `${current.entity}:${parentPath}`; if (pathStack.includes(pathIdentifier)) { return []; } pathStack.push(pathIdentifier); if (visited.has(current.entity)) { pathStack.pop(); return []; } visited.add(current.entity); for (const parentEntity of current.parents) { const newParentPath = parentPath ? `${parentEntity.targetProp}.${parentPath}` : parentEntity.targetProp; const newVisited = new Set(visited); const newPathStack = [...pathStack]; const parentAliases = recursivelyBuildAliasPath(parentEntity.ref, newParentPath, [], newVisited, newPathStack).map((aliasObj) => ({ alias: aliasObj.alias, isInverse: parentEntity.isInverse, isList: parentEntity.isList, })); aliases.push(...parentAliases); // Handle shortcut paths via inSchemaRef if (parentEntity.inSchemaRef) { const shortCutOf = parentAliases[0]; if (!shortCutOf) { continue; } const shortcutAliases_ = recursivelyBuildAliasPath(parentEntity.inSchemaRef, newParentPath, [], new Set(visited), [...pathStack]); // Assign all shortcut aliases to the parent aliases const shortcutAliases = []; shortcutAliases_.forEach((shortcutAlias) => { parentAliases.forEach((aliasObj) => { let isSamePath = aliasObj.alias.split(".")[0] === shortcutAlias.alias.split(".")[0]; if (!isSamePath) { return; } shortcutAliases.push({ alias: shortcutAlias.alias, shortCutOf: aliasObj.alias, isList: parentEntity.isList ?? true, isInverse: shortcutAlias.isInverse, }); }); }); // Only add shortcut aliases if they don’t duplicate existing paths for (const shortcut of shortcutAliases) { if (!aliases.some((a) => a.alias === shortcut?.alias)) { aliases.push(shortcut); } } } } // Add the current entity's alias, avoiding duplication const pathSegments = parentPath ? parentPath.split(".") : []; const baseAlias = pathSegments.length && pathSegments[0] === current.alias ? parentPath // If parentPath already starts with this alias, use it as-is : `${current.alias}${parentPath ? `.${parentPath}` : ""}`; if (!aliases.some((a) => a.alias === baseAlias)) { aliases.push({ alias: baseAlias, isInverse: false, }); } pathStack.pop(); visited.delete(current.entity); return aliases; } for (const objectRepresentationKey of Object.keys(objectRepresentation).filter((key) => !_types_1.schemaObjectRepresentationPropertiesToOmit.includes(key))) { const entityRepresentationRef = objectRepresentation[objectRepresentationKey]; const aliases = recursivelyBuildAliasPath(entityRepresentationRef); for (const alias of aliases) { if (aliasMap[alias.alias] && !alias.shortCutOf) { continue; } aliasMap[alias.alias] = { ref: entityRepresentationRef, shortCutOf: alias.shortCutOf, isList: alias.isList, isInverse: alias.isInverse, }; } } return aliasMap; } function buildSchemaFromFilterableLinks(moduleJoinerConfigs, servicesEntityMap) { const allFilterable = moduleJoinerConfigs.flatMap((config) => { const entities = []; const schema = config.schema; if (config.isLink) { if (!config.relationships?.some((r) => r.filterable?.length)) { return []; } for (const relationship of config.relationships) { relationship.filterable ??= []; if (!relationship.filterable?.length || !relationship.filterable?.includes("id")) { relationship.filterable.push("id"); } const fieldAliasMap = {}; for (const extend of config.extends ?? []) { fieldAliasMap[extend.serviceName] = Object.keys(extend.fieldAlias ?? {}); fieldAliasMap[extend.serviceName].push(extend.relationship.alias); } const serviceName = relationship.serviceName; entities.push({ serviceName, entity: relationship.entity, fields: Array.from(new Set(relationship.filterable.concat(fieldAliasMap[serviceName] ?? []))), schema, }); } return entities; } let aliases = config.alias ?? []; aliases = (Array.isArray(aliases) ? aliases : [aliases]).filter((a) => a.filterable?.length && a.entity); for (const alias of aliases) { entities.push({ serviceName: config.serviceName, entity: alias.entity, fields: Array.from(new Set(alias.filterable)), schema, }); } return entities; }); const getGqlType = (entity, field) => { const fieldRef = servicesEntityMap[entity]?._fields?.[field]; if (!fieldRef) { return; } const isEnum = fieldRef.type?.astNode?.kind === utils_1.GraphQLUtils.Kind.ENUM_TYPE_DEFINITION; const fieldType = isEnum ? "String" : fieldRef.type.toString(); const isArray = fieldType.startsWith("["); const currentType = fieldType.replace(/\[|\]|\!/g, ""); return isArray ? `[${currentType}]` : currentType; }; const schema = allFilterable .map(({ serviceName, entity, fields, schema }) => { if (!schema) { return; } const normalizedEntity = (0, utils_1.lowerCaseFirst)((0, utils_1.kebabCase)(entity)); const events = `@Listeners(values: ["${serviceName}.${normalizedEntity}.created", "${serviceName}.${normalizedEntity}.updated", "${serviceName}.${normalizedEntity}.deleted"])`; const fieldDefinitions = fields .map((field) => { const type = getGqlType(entity, field) ?? "String"; return ` ${field}: ${type}`; }) .join("\n"); return `type ${entity} ${events} { ${fieldDefinitions} }`; }) .join("\n\n"); return schema; } /** * This util build an internal representation object from the provided schema. * It will resolve all modules, fields, link module representation to build * the appropriate representation for the index module. * * This representation will be used to re construct the expected output object from a search * but can also be used for anything since the relation tree is available through ref. * * @param schema */ function buildSchemaObjectRepresentation(schema) { const moduleJoinerConfigs = modules_sdk_1.MedusaModule.getAllJoinerConfigs(); const servicesEntityMap = getServicesEntityMap(moduleJoinerConfigs); const filterableEntities = buildSchemaFromFilterableLinks(moduleJoinerConfigs, servicesEntityMap); const augmentedSchema = exports.CustomDirectives.Listeners.definition + "\n" + schema + "\n" + filterableEntities; const executableSchema = makeSchemaExecutable(augmentedSchema); const entitiesMap = executableSchema.getTypeMap(); const objectRepresentation = { _serviceNameModuleConfigMap: {}, }; Object.entries(entitiesMap).forEach(([entityName, entityMapValue]) => { if (!entityMapValue.astNode || entityMapValue.astNode.kind === utils_1.GraphQLUtils.Kind.SCALAR_TYPE_DEFINITION) { return; } processEntity(entityName, { entitiesMap, servicesEntityMap, moduleJoinerConfigs, objectRepresentationRef: objectRepresentation, }); }); objectRepresentation._schemaPropertiesMap = buildAliasMap(objectRepresentation); return { objectRepresentation, entitiesMap, executableSchema, }; } //# sourceMappingURL=build-config.js.map