@vendure/core
Version:
A modern, headless ecommerce framework
284 lines • 14.4 kB
JavaScript
;
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