UNPKG

@vendure/core

Version:

A modern, headless ecommerce framework

284 lines 14.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateResolvers = generateResolvers; const generated_types_1 = require("@vendure/common/lib/generated-types"); const graphql_scalars_1 = require("graphql-scalars"); const errors_1 = require("../../common/error/errors"); const generated_graphql_admin_errors_1 = require("../../common/error/generated-graphql-admin-errors"); const generated_graphql_shop_errors_1 = require("../../common/error/generated-graphql-shop-errors"); const vendure_logger_1 = require("../../config/logger/vendure-logger"); const plugin_metadata_1 = require("../../plugin/plugin-metadata"); const request_context_1 = require("../common/request-context"); const user_has_permissions_on_custom_field_1 = require("../common/user-has-permissions-on-custom-field"); const get_custom_fields_config_without_interfaces_1 = require("./get-custom-fields-config-without-interfaces"); const money_scalar_1 = require("./money-scalar"); /** * @description * Generates additional resolvers required for things like resolution of union types, * custom scalars and "relation"-type custom fields. */ async function generateResolvers(configService, customFieldRelationResolverService, apiType, schema) { // Prevent `Type "Node" is missing a "resolveType" resolver.` warnings. // See https://github.com/apollographql/apollo-server/issues/1075 const dummyResolveType = { __resolveType() { return null; }, }; const stockMovementResolveType = { __resolveType(value) { switch (value.type) { case generated_types_1.StockMovementType.ADJUSTMENT: return 'StockAdjustment'; case generated_types_1.StockMovementType.ALLOCATION: return 'Allocation'; case generated_types_1.StockMovementType.SALE: return 'Sale'; case generated_types_1.StockMovementType.CANCELLATION: return 'Cancellation'; case generated_types_1.StockMovementType.RETURN: return 'Return'; case generated_types_1.StockMovementType.RELEASE: return 'Release'; } }, }; const regionResolveType = { __resolveType(value) { switch (value.type) { case 'country': return 'Country'; case 'province': return 'Province'; default: { throw new errors_1.InternalServerError(`No __resolveType defined for Region type "${value.type}"`); } } }, }; const customFieldsConfigResolveType = { __resolveType(value) { switch (value.type) { case 'string': return 'StringCustomFieldConfig'; case 'localeString': return 'LocaleStringCustomFieldConfig'; case 'text': return 'TextCustomFieldConfig'; case 'localeText': return 'LocaleTextCustomFieldConfig'; case 'int': return 'IntCustomFieldConfig'; case 'float': return 'FloatCustomFieldConfig'; case 'boolean': return 'BooleanCustomFieldConfig'; case 'datetime': return 'DateTimeCustomFieldConfig'; case 'relation': return 'RelationCustomFieldConfig'; case 'struct': return 'StructCustomFieldConfig'; } }, }; const structFieldConfigResolveType = { __resolveType(value) { switch (value.type) { case 'string': return 'StringStructFieldConfig'; case 'text': return 'TextStructFieldConfig'; case 'int': return 'IntStructFieldConfig'; case 'float': return 'FloatStructFieldConfig'; case 'boolean': return 'BooleanStructFieldConfig'; case 'datetime': return 'DateTimeStructFieldConfig'; } }, }; // @ts-ignore const { default: GraphQLUpload } = await import('graphql-upload/GraphQLUpload.mjs'); const commonResolvers = { JSON: graphql_scalars_1.GraphQLJSON, DateTime: graphql_scalars_1.GraphQLDateTime, Money: money_scalar_1.GraphQLMoney, Node: dummyResolveType, PaginatedList: dummyResolveType, Upload: GraphQLUpload || dummyResolveType, SearchResultPrice: { __resolveType(value) { return value.hasOwnProperty('value') ? 'SinglePrice' : 'PriceRange'; }, }, CustomFieldConfig: customFieldsConfigResolveType, CustomField: customFieldsConfigResolveType, StructFieldConfig: structFieldConfigResolveType, ErrorResult: { __resolveType(value) { return value.__typename; }, }, Region: regionResolveType, }; const customFieldResolvers = generateCustomFieldResolvers(configService, customFieldRelationResolverService, schema); const adminResolvers = Object.assign(Object.assign({ StockMovementItem: stockMovementResolveType, StockMovement: stockMovementResolveType }, generated_graphql_admin_errors_1.adminErrorOperationTypeResolvers), customFieldResolvers.adminResolvers); const shopResolvers = Object.assign(Object.assign({}, generated_graphql_shop_errors_1.shopErrorOperationTypeResolvers), customFieldResolvers.shopResolvers); const resolvers = apiType === 'admin' ? Object.assign(Object.assign(Object.assign({}, commonResolvers), adminResolvers), getCustomScalars(configService, 'admin')) : Object.assign(Object.assign(Object.assign({}, commonResolvers), shopResolvers), getCustomScalars(configService, 'shop')); return resolvers; } /** * @description * Based on the CustomFields config, this function dynamically creates resolver functions to handle * the resolution of more complex custom field types: * * - `relation` type custom fields: These are resolved by querying the database for the related entity. * - `struct` type custom fields: These are resolved by iterating over the fields of the struct and * resolving each field individually. * * This function also performs additional checks to ensure that only users with the correct permissions * are able to access custom fields, and that custom field data is not being leaked via the JSON type. */ function generateCustomFieldResolvers(configService, customFieldRelationResolverService, schema) { const ENTITY_ID_KEY = '__entityId__'; const adminResolvers = {}; const shopResolvers = {}; const customFieldsConfig = (0, get_custom_fields_config_without_interfaces_1.getCustomFieldsConfigWithoutInterfaces)(configService.customFields, schema); for (const [entityName, customFields] of customFieldsConfig) { if (!schema.getType(entityName)) { continue; } const customFieldTypeName = `${entityName}CustomFields`; // Some types are not exposed in the Shop API and therefore defining resolvers // for them would lead to an Apollo error on bootstrap. const excludeFromShopApi = ['GlobalSettings'].includes(entityName); // In order to resolve the relations in the CustomFields type, we need // access to the entity id. Therefore, we attach it to the resolved value // so that it is available to the `relationResolver` below. const customFieldResolver = (source) => { return Object.assign(Object.assign({}, source.customFields), { [ENTITY_ID_KEY]: source.id }); }; const resolverObject = { customFields: customFieldResolver, }; adminResolvers[entityName] = resolverObject; if (!excludeFromShopApi) { shopResolvers[entityName] = resolverObject; if (entityName === 'ShippingMethod') { shopResolvers.ShippingMethodQuote = resolverObject; } if (entityName === 'PaymentMethod') { shopResolvers.PaymentMethodQuote = resolverObject; } } for (const fieldDef of customFields) { if (fieldDef.internal === true) { // Do not create any resolvers for internal relations continue; } let resolver; if (isRelationalType(fieldDef)) { resolver = async (source, args, context) => { const ctx = (0, request_context_1.internal_getRequestContext)(context.req); if (!(0, user_has_permissions_on_custom_field_1.userHasPermissionsOnCustomField)(ctx, fieldDef)) { return null; } const eagerEntity = source[fieldDef.name]; // If the relation is eager-loaded, we can simply try to translate this relation entity if they have translations if (eagerEntity != null) { try { return await customFieldRelationResolverService.translateEntity(ctx, eagerEntity, fieldDef); } catch (e) { vendure_logger_1.Logger.debug(`Error resolving eager-loaded custom field entity relation "${entityName}.${fieldDef.name}": ${e.message}`); } } const entityId = source[ENTITY_ID_KEY]; return customFieldRelationResolverService.resolveRelation({ ctx, fieldDef, entityName, entityId, }); }; } else if (isStructType(fieldDef)) { resolver = async (source) => { const fields = fieldDef.fields; function buildStructObject(valueFromDb) { const result = {}; for (const structField of fields) { const value = valueFromDb === null || valueFromDb === void 0 ? void 0 : valueFromDb[structField.name]; if (structField.list === true) { const valueArray = Array.isArray(value) ? value : value ? [value] : []; result[structField.name] = valueArray; } else { result[structField.name] = value !== null && value !== void 0 ? value : null; } } return result; } if (fieldDef.list === true) { const structArray = Array.isArray(source[fieldDef.name]) ? source[fieldDef.name] : source[fieldDef.name] ? [source[fieldDef.name]] : []; source[fieldDef.name] = structArray.map(buildStructObject); } else { source[fieldDef.name] = buildStructObject(source[fieldDef.name]); } return source[fieldDef.name]; }; adminResolvers[customFieldTypeName] = Object.assign(Object.assign({}, adminResolvers[customFieldTypeName]), { [fieldDef.name]: resolver }); if (fieldDef.public !== false && !excludeFromShopApi) { shopResolvers[customFieldTypeName] = Object.assign(Object.assign({}, shopResolvers[customFieldTypeName]), { [fieldDef.name]: resolver }); } } else { resolver = async (source, args, context) => { const ctx = (0, request_context_1.internal_getRequestContext)(context.req); if (!(0, user_has_permissions_on_custom_field_1.userHasPermissionsOnCustomField)(ctx, fieldDef)) { return null; } return source[fieldDef.name]; }; } adminResolvers[customFieldTypeName] = Object.assign(Object.assign({}, adminResolvers[customFieldTypeName]), { [fieldDef.name]: resolver }); if (fieldDef.public !== false && !excludeFromShopApi) { shopResolvers[customFieldTypeName] = Object.assign(Object.assign({}, shopResolvers[customFieldTypeName]), { [fieldDef.name]: resolver }); } } const allCustomFieldsAreNonPublic = customFields.length && customFields.every(f => f.public === false || f.internal === true); if (allCustomFieldsAreNonPublic) { // When an entity has only non-public custom fields, the GraphQL type used for the // customFields field is `JSON`. This type will simply return the full object, which // will cause a leak of private data unless we force a `null` return value in the case // that there are no public fields. // See https://github.com/vendure-ecommerce/vendure/issues/3049 shopResolvers[entityName] = { customFields: () => null }; } } return { adminResolvers, shopResolvers }; } function getCustomScalars(configService, apiType) { return (0, plugin_metadata_1.getPluginAPIExtensions)(configService.plugins, apiType) .map(e => { var _a; return (typeof e.scalars === 'function' ? e.scalars() : ((_a = e.scalars) !== null && _a !== void 0 ? _a : {})); }) .reduce((all, scalarMap) => (Object.assign(Object.assign({}, all), scalarMap)), {}); } function isRelationalType(input) { return input.type === 'relation'; } function isStructType(input) { return input.type === 'struct'; } function isTranslatable(input) { return typeof input === 'object' && input != null && input.hasOwnProperty('translations'); } //# sourceMappingURL=generate-resolvers.js.map