UNPKG

@vendure/core

Version:

A modern, headless ecommerce framework

644 lines (635 loc) 28.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addGraphQLCustomFields = addGraphQLCustomFields; exports.addServerConfigCustomFields = addServerConfigCustomFields; exports.addActiveAdministratorCustomFields = addActiveAdministratorCustomFields; exports.addRegisterCustomerCustomFieldsInput = addRegisterCustomerCustomFieldsInput; exports.addModifyOrderCustomFields = addModifyOrderCustomFields; exports.addOrderLineCustomFieldsInput = addOrderLineCustomFieldsInput; exports.addShippingMethodQuoteCustomFields = addShippingMethodQuoteCustomFields; exports.addPaymentMethodQuoteCustomFields = addPaymentMethodQuoteCustomFields; const shared_utils_1 = require("@vendure/common/lib/shared-utils"); const graphql_1 = require("graphql"); const vendure_logger_1 = require("../../config/logger/vendure-logger"); const get_custom_fields_config_without_interfaces_1 = require("./get-custom-fields-config-without-interfaces"); /** * Given a CustomFields config object, generates an SDL string extending the built-in * types with a customFields property for all entities, translations and inputs for which * custom fields are defined. */ function addGraphQLCustomFields(typeDefsOrSchema, customFieldConfig, publicOnly) { var _a; const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; let customFieldTypeDefs = ''; if (!schema.getType('JSON')) { customFieldTypeDefs += ` scalar JSON `; } if (!schema.getType('DateTime')) { customFieldTypeDefs += ` scalar DateTime `; } const customFieldsConfig = (0, get_custom_fields_config_without_interfaces_1.getCustomFieldsConfigWithoutInterfaces)(customFieldConfig, schema); const entitiesWithPublicTypes = [`ShippingMethod`, `PaymentMethod`]; for (const [entityName, customFields] of customFieldsConfig) { const gqlType = schema.getType(entityName); if ((0, graphql_1.isObjectType)(gqlType) && gqlType.getFields().customFields) { vendure_logger_1.Logger.warn(`The entity type "${entityName}" already has a "customFields" field defined. Skipping automatic custom field extension.`); continue; } const customEntityFields = customFields.filter(config => { return !config.internal && (publicOnly === true ? config.public !== false : true); }); for (const fieldDef of customEntityFields) { if (fieldDef.type === 'relation') { const graphQlTypeName = fieldDef.graphQLType || fieldDef.entity.name; if (!schema.getType(graphQlTypeName)) { const customFieldPath = `${entityName}.${fieldDef.name}`; const errorMessage = `The GraphQL type "${graphQlTypeName !== null && graphQlTypeName !== void 0 ? graphQlTypeName : '(unknown)'}" specified by the ${customFieldPath} custom field does not exist in the ${publicOnly ? 'Shop API' : 'Admin API'} schema.`; vendure_logger_1.Logger.warn(errorMessage); if (publicOnly) { vendure_logger_1.Logger.warn([ `This can be resolved by either:`, ` - setting \`public: false\` in the ${customFieldPath} custom field config`, ` - defining the "${graphQlTypeName}" type in the Shop API schema`, ].join('\n')); } throw new Error(errorMessage); } } } const localizedFields = customEntityFields.filter(field => field.type === 'localeString' || field.type === 'localeText'); const nonLocalizedFields = customEntityFields.filter(field => field.type !== 'localeString' && field.type !== 'localeText'); const writeableLocalizedFields = localizedFields.filter(field => !field.readonly); const writeableNonLocalizedFields = nonLocalizedFields.filter(field => !field.readonly); const sortableFields = customEntityFields.filter(field => field.list !== true && field.type !== 'struct'); const filterableFields = customEntityFields.filter(field => field.type !== 'relation' && field.type !== 'struct'); const structCustomFields = customEntityFields.filter((f) => f.type === 'struct'); if (schema.getType(entityName)) { if (customEntityFields.length) { for (const structCustomField of structCustomFields) { customFieldTypeDefs += ` type ${getStructTypeName(entityName, structCustomField)} { ${mapToStructFields(structCustomField.fields, wrapListType(getGraphQlTypeForStructField))} } `; } customFieldTypeDefs += ` type ${entityName}CustomFields { ${mapToFields(customEntityFields, wrapListType(getGraphQlType(entityName)))} } extend type ${entityName} { customFields: ${entityName}CustomFields } `; } else { customFieldTypeDefs += ` extend type ${entityName} { customFields: JSON } `; } } if (localizedFields.length && schema.getType(`${entityName}Translation`)) { customFieldTypeDefs += ` type ${entityName}TranslationCustomFields { ${mapToFields(localizedFields, wrapListType(getGraphQlType(entityName)))} } extend type ${entityName}Translation { customFields: ${entityName}TranslationCustomFields } `; } const hasCreateInputType = schema.getType(`Create${entityName}Input`); const hasUpdateInputType = schema.getType(`Update${entityName}Input`); if ((hasCreateInputType || hasUpdateInputType) && writeableNonLocalizedFields.length) { // Define any Struct input types that are required by // the create and/or update input types. for (const structCustomField of structCustomFields) { customFieldTypeDefs += ` input ${getStructInputName(entityName, structCustomField)} { ${mapToStructFields(structCustomField.fields, wrapListType(getGraphQlInputType(entityName)))} } `; } } if (hasCreateInputType) { if (writeableNonLocalizedFields.length) { customFieldTypeDefs += ` input Create${entityName}CustomFieldsInput { ${mapToFields(writeableNonLocalizedFields, wrapListType(getGraphQlInputType(entityName)), shared_utils_1.getGraphQlInputName)} } extend input Create${entityName}Input { customFields: Create${entityName}CustomFieldsInput } `; } else { customFieldTypeDefs += ` extend input Create${entityName}Input { customFields: JSON } `; } } if (hasUpdateInputType) { if (writeableNonLocalizedFields.length) { customFieldTypeDefs += ` input Update${entityName}CustomFieldsInput { ${mapToFields(writeableNonLocalizedFields, wrapListType(getGraphQlInputType(entityName)), shared_utils_1.getGraphQlInputName)} } extend input Update${entityName}Input { customFields: Update${entityName}CustomFieldsInput } `; } else { customFieldTypeDefs += ` extend input Update${entityName}Input { customFields: JSON } `; } } if (sortableFields.length && schema.getType(`${entityName}SortParameter`)) { // Sorting list fields makes no sense, so we only add "sort" fields // to non-list fields. customFieldTypeDefs += ` extend input ${entityName}SortParameter { ${mapToFields(sortableFields, () => 'SortOrder')} } `; } if (filterableFields.length && schema.getType(`${entityName}FilterParameter`)) { customFieldTypeDefs += ` extend input ${entityName}FilterParameter { ${mapToFields(filterableFields, getFilterOperator)} } `; } if (writeableLocalizedFields) { const translationInputs = [ `${entityName}TranslationInput`, `Create${entityName}TranslationInput`, `Update${entityName}TranslationInput`, ]; for (const inputName of translationInputs) { if (schema.getType(inputName)) { if (writeableLocalizedFields.length) { customFieldTypeDefs += ` input ${inputName}CustomFields { ${mapToFields(writeableLocalizedFields, wrapListType(getGraphQlType(entityName)))} } extend input ${inputName} { customFields: ${inputName}CustomFields } `; } else { customFieldTypeDefs += ` extend input ${inputName} { customFields: JSON } `; } } } } const publicEntityName = `Public${entityName}`; if (schema.getType(publicEntityName) && entitiesWithPublicTypes.includes(entityName)) { if (customEntityFields.length) { for (const structCustomField of structCustomFields) { customFieldTypeDefs += ` type ${getStructTypeName(publicEntityName, structCustomField)} { ${mapToStructFields(structCustomField.fields, wrapListType(getGraphQlTypeForStructField))} } `; } customFieldTypeDefs += ` type ${publicEntityName}CustomFields { ${mapToFields(customEntityFields, wrapListType(getGraphQlType(entityName)))} } extend type ${publicEntityName} { customFields: ${publicEntityName}CustomFields } `; } else { customFieldTypeDefs += ` extend type ${publicEntityName} { customFields: JSON } `; } } } const publicAddressFields = (_a = customFieldConfig.Address) === null || _a === void 0 ? void 0 : _a.filter(config => !config.internal && (publicOnly === true ? config.public !== false : true)); const writeablePublicAddressFields = publicAddressFields === null || publicAddressFields === void 0 ? void 0 : publicAddressFields.filter(field => !field.readonly); if (publicAddressFields === null || publicAddressFields === void 0 ? void 0 : publicAddressFields.length) { // For custom fields on the Address entity, we also extend the OrderAddress // type (which is used to store address snapshots on Orders) if (schema.getType('OrderAddress')) { customFieldTypeDefs += ` extend type OrderAddress { customFields: AddressCustomFields } `; } if (schema.getType('UpdateOrderAddressInput') && (writeablePublicAddressFields === null || writeablePublicAddressFields === void 0 ? void 0 : writeablePublicAddressFields.length)) { customFieldTypeDefs += ` extend input UpdateOrderAddressInput { customFields: UpdateAddressCustomFieldsInput } `; } } else { if (schema.getType('OrderAddress')) { customFieldTypeDefs += ` extend type OrderAddress { customFields: JSON } `; } } return (0, graphql_1.extendSchema)(schema, (0, graphql_1.parse)(customFieldTypeDefs)); } function addServerConfigCustomFields(typeDefsOrSchema, customFieldConfig) { const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; const customFieldTypeDefs = ` """ This type is deprecated in v2.2 in favor of the EntityCustomFields type, which allows custom fields to be defined on user-supplied entities. """ type CustomFields { ${Object.keys(customFieldConfig).reduce((output, name) => output + name + ': [CustomFieldConfig!]!\n', '')} } type EntityCustomFields { entityName: String! customFields: [CustomFieldConfig!]! } extend type ServerConfig { """ This field is deprecated in v2.2 in favor of the entityCustomFields field, which allows custom fields to be defined on user-supplies entities. """ customFieldConfig: CustomFields! entityCustomFields: [EntityCustomFields!]! } `; return (0, graphql_1.extendSchema)(schema, (0, graphql_1.parse)(customFieldTypeDefs)); } function addActiveAdministratorCustomFields(typeDefsOrSchema, administratorCustomFields) { const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; const writableCustomFields = administratorCustomFields === null || administratorCustomFields === void 0 ? void 0 : administratorCustomFields.filter(field => field.readonly !== true && field.internal !== true); const extension = ` extend input UpdateActiveAdministratorInput { customFields: ${0 < (writableCustomFields === null || writableCustomFields === void 0 ? void 0 : writableCustomFields.length) ? 'UpdateAdministratorCustomFieldsInput' : 'JSON'} } `; return (0, graphql_1.extendSchema)(schema, (0, graphql_1.parse)(extension)); } /** * If CustomFields are defined on the Customer entity, then an extra `customFields` field is added to * the `RegisterCustomerInput` so that public writable custom fields can be set when a new customer * is registered. */ function addRegisterCustomerCustomFieldsInput(typeDefsOrSchema, customerCustomFields) { const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; if (!customerCustomFields || customerCustomFields.length === 0) { return schema; } const publicWritableCustomFields = customerCustomFields.filter(fieldDef => { return fieldDef.public !== false && !fieldDef.readonly && !fieldDef.internal; }); if (publicWritableCustomFields.length < 1) { return schema; } const customFieldTypeDefs = ` input RegisterCustomerCustomFieldsInput { ${mapToFields(publicWritableCustomFields, wrapListType(getGraphQlInputType('Customer')), shared_utils_1.getGraphQlInputName)} } extend input RegisterCustomerInput { customFields: RegisterCustomerCustomFieldsInput } `; return (0, graphql_1.extendSchema)(schema, (0, graphql_1.parse)(customFieldTypeDefs)); } /** * If CustomFields are defined on the Order entity, we add a `customFields` field to the ModifyOrderInput * type. */ function addModifyOrderCustomFields(typeDefsOrSchema, orderCustomFields) { const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; if (!orderCustomFields || orderCustomFields.length === 0) { return schema; } if (schema.getType('ModifyOrderInput') && schema.getType('UpdateOrderCustomFieldsInput')) { const customFieldTypeDefs = ` extend input ModifyOrderInput { customFields: UpdateOrderCustomFieldsInput } `; return (0, graphql_1.extendSchema)(schema, (0, graphql_1.parse)(customFieldTypeDefs)); } return schema; } /** * If CustomFields are defined on the OrderLine entity, then an extra `customFields` argument * must be added to the `addItemToOrder` and `adjustOrderLine` mutations, as well as the related * fields in the `ModifyOrderInput` type. */ function addOrderLineCustomFieldsInput(typeDefsOrSchema, orderLineCustomFields, publicOnly) { const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; orderLineCustomFields = orderLineCustomFields.filter(f => f.internal !== true); const publicCustomFields = orderLineCustomFields.filter(f => f.public !== false); const customFields = publicOnly ? publicCustomFields : orderLineCustomFields; if (!customFields || customFields.length === 0) { return schema; } const schemaConfig = schema.toConfig(); const mutationType = schemaConfig.mutation; if (!mutationType) { return schema; } const structFields = orderLineCustomFields.filter((f) => f.type === 'struct'); const structInputTypes = []; if (0 < structFields.length) { for (const structField of structFields) { const structInputName = getStructInputName('OrderLine', structField); structInputTypes.push(new graphql_1.GraphQLInputObjectType({ name: structInputName, fields: structField.fields.reduce((fields, field) => { const name = (0, shared_utils_1.getGraphQlInputName)(field); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const primitiveType = schema.getType(getGraphQlInputType('OrderLine')(field)); const type = field.list === true ? new graphql_1.GraphQLList(primitiveType) : primitiveType; return Object.assign(Object.assign({}, fields), { [name]: { type } }); }, {}), })); } } const input = new graphql_1.GraphQLInputObjectType({ name: 'OrderLineCustomFieldsInput', fields: customFields.reduce((fields, field) => { var _a; const name = (0, shared_utils_1.getGraphQlInputName)(field); const inputTypeName = getGraphQlInputType('OrderLine')(field); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const inputType = (_a = schema.getType(getGraphQlInputType('OrderLine')(field))) !== null && _a !== void 0 ? _a : structInputTypes.find(t => t.name === inputTypeName); if (!inputType) { throw new Error(`Could not find input type for field ${field.name}`); } const type = field.list === true ? new graphql_1.GraphQLList(inputType) : inputType; return Object.assign(Object.assign({}, fields), { [name]: { type } }); }, {}), }); schemaConfig.types = [...schemaConfig.types, ...structInputTypes, input]; const addItemToOrderMutation = mutationType.getFields().addItemToOrder; const adjustOrderLineMutation = mutationType.getFields().adjustOrderLine; if (addItemToOrderMutation) { addItemToOrderMutation.args = [ ...addItemToOrderMutation.args, { name: 'customFields', type: input, description: null, defaultValue: null, extensions: {}, astNode: null, deprecationReason: null, }, ]; } if (adjustOrderLineMutation) { adjustOrderLineMutation.args = [ ...adjustOrderLineMutation.args, { name: 'customFields', type: input, description: null, defaultValue: null, extensions: {}, astNode: null, deprecationReason: null, }, ]; } let extendedSchema = new graphql_1.GraphQLSchema(schemaConfig); if (schema.getType('AddItemInput')) { const customFieldTypeDefs = ` extend input AddItemInput { customFields: OrderLineCustomFieldsInput } `; extendedSchema = (0, graphql_1.extendSchema)(extendedSchema, (0, graphql_1.parse)(customFieldTypeDefs)); } if (schema.getType('OrderLineInput')) { const customFieldTypeDefs = ` extend input OrderLineInput { customFields: OrderLineCustomFieldsInput } `; extendedSchema = (0, graphql_1.extendSchema)(extendedSchema, (0, graphql_1.parse)(customFieldTypeDefs)); } if (schema.getType('AddItemToDraftOrderInput')) { const customFieldTypeDefs = ` extend input AddItemToDraftOrderInput { customFields: OrderLineCustomFieldsInput } `; extendedSchema = (0, graphql_1.extendSchema)(extendedSchema, (0, graphql_1.parse)(customFieldTypeDefs)); } if (schema.getType('AdjustDraftOrderLineInput')) { const customFieldTypeDefs = ` extend input AdjustDraftOrderLineInput { customFields: OrderLineCustomFieldsInput } `; extendedSchema = (0, graphql_1.extendSchema)(extendedSchema, (0, graphql_1.parse)(customFieldTypeDefs)); } return extendedSchema; } function addShippingMethodQuoteCustomFields(typeDefsOrSchema, shippingMethodCustomFields) { const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; let customFieldTypeDefs = ''; const publicCustomFields = shippingMethodCustomFields.filter(f => f.public !== false); if (0 < publicCustomFields.length) { customFieldTypeDefs = ` extend type ShippingMethodQuote { customFields: ShippingMethodCustomFields } `; } else { customFieldTypeDefs = ` extend type ShippingMethodQuote { customFields: JSON } `; } return (0, graphql_1.extendSchema)(schema, (0, graphql_1.parse)(customFieldTypeDefs)); } function addPaymentMethodQuoteCustomFields(typeDefsOrSchema, paymentMethodCustomFields) { const schema = typeof typeDefsOrSchema === 'string' ? (0, graphql_1.buildSchema)(typeDefsOrSchema) : typeDefsOrSchema; let customFieldTypeDefs = ''; const publicCustomFields = paymentMethodCustomFields.filter(f => f.public !== false); if (0 < publicCustomFields.length) { customFieldTypeDefs = ` extend type PaymentMethodQuote { customFields: PaymentMethodCustomFields } `; } else { customFieldTypeDefs = ` extend type PaymentMethodQuote { customFields: JSON } `; } return (0, graphql_1.extendSchema)(schema, (0, graphql_1.parse)(customFieldTypeDefs)); } /** * Maps an array of CustomFieldConfig objects into a string of SDL fields. */ function mapToFields(fieldDefs, typeFn, nameFn) { const res = fieldDefs .map(field => { const type = typeFn(field); if (!type) { return; } const name = nameFn ? nameFn(field) : field.name; const deprecationDirective = getDeprecationDirective(field); return `${name}: ${type} ${deprecationDirective}`; }) .filter(x => x != null); return res.join('\n'); } /** * Maps an array of CustomFieldConfig objects into a string of SDL fields. */ function mapToStructFields(fieldDefs, typeFn, nameFn) { const res = fieldDefs .map(field => { const type = typeFn(field); if (!type) { return; } const name = nameFn ? nameFn(field) : field.name; // Note: Struct fields don't currently support deprecation in the type system, // but we keep this consistent for future extensibility return `${name}: ${type}`; }) .filter(x => x != null); return res.join('\n'); } function getFilterOperator(config) { switch (config.type) { case 'datetime': return config.list ? 'DateListOperators' : 'DateOperators'; case 'string': case 'localeString': case 'text': case 'localeText': return config.list ? 'StringListOperators' : 'StringOperators'; case 'boolean': return config.list ? 'BooleanListOperators' : 'BooleanOperators'; case 'int': case 'float': return config.list ? 'NumberListOperators' : 'NumberOperators'; case 'relation': case 'struct': return undefined; default: (0, shared_utils_1.assertNever)(config); } } function getGraphQlInputType(entityName) { return (config) => { switch (config.type) { case 'relation': return 'ID'; case 'struct': return getStructInputName(entityName, config); default: return getGraphQlType(entityName)(config); } }; } function wrapListType(getTypeFn) { return (def) => { const type = getTypeFn(def); if (!type) { return; } return def.list ? `[${type}!]` : type; }; } function getGraphQlType(entityName) { return (config) => { switch (config.type) { case 'string': case 'localeString': case 'text': case 'localeText': return 'String'; case 'datetime': return 'DateTime'; case 'boolean': return 'Boolean'; case 'int': return 'Int'; case 'float': return 'Float'; case 'relation': return config.graphQLType || config.entity.name; case 'struct': return getStructTypeName(entityName, config); default: (0, shared_utils_1.assertNever)(config); } }; } function getGraphQlTypeForStructField(config) { switch (config.type) { case 'string': case 'text': return 'String'; case 'datetime': return 'DateTime'; case 'boolean': return 'Boolean'; case 'int': return 'Int'; case 'float': return 'Float'; default: (0, shared_utils_1.assertNever)(config); } } function getStructTypeName(entityName, fieldDef) { return `${entityName}${pascalCase(fieldDef.name)}Struct`; } function getStructInputName(entityName, fieldDef) { return `${entityName}${pascalCase(fieldDef.name)}StructInput`; } function pascalCase(input) { return input.charAt(0).toUpperCase() + input.slice(1); } function getDeprecationDirective(field) { if (!field.deprecated) { return ''; } if (typeof field.deprecated === 'string') { // Escape quotes in the deprecation reason const escapedReason = field.deprecated.replace(/"/g, '\\"'); return `@deprecated(reason: "${escapedReason}")`; } return '@deprecated'; } //# sourceMappingURL=graphql-custom-fields.js.map