@vendure/core
Version:
A modern, headless ecommerce framework
644 lines (635 loc) • 28.3 kB
JavaScript
;
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