@vendure/core
Version:
A modern, headless ecommerce framework
258 lines • 12.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerCustomEntityFields = registerCustomEntityFields;
const shared_utils_1 = require("@vendure/common/lib/shared-utils");
const typeorm_1 = require("typeorm");
const DateUtils_1 = require("typeorm/util/DateUtils");
const vendure_logger_1 = require("../config/logger/vendure-logger");
/**
* The maximum length of the "length" argument of a MySQL varchar column.
*/
const MAX_STRING_LENGTH = 65535;
/**
* Dynamically add columns to the custom field entity based on the CustomFields config.
*/
function registerCustomFieldsForEntity(config, entityName,
// eslint-disable-next-line @typescript-eslint/prefer-function-type
ctor, translation = false) {
const customFields = config.customFields && config.customFields[entityName];
const dbEngine = config.dbConnectionOptions.type;
if (customFields) {
for (const customField of customFields) {
const { name, list, defaultValue, nullable } = customField;
const instance = new ctor();
const registerColumn = () => {
var _a;
if (customField.type === 'relation') {
if (customField.list) {
(0, typeorm_1.ManyToMany)(type => customField.entity, customField.inverseSide, {
eager: customField.eager,
})(instance, name);
(0, typeorm_1.JoinTable)()(instance, name);
}
else {
(0, typeorm_1.ManyToOne)(type => customField.entity, customField.inverseSide, {
eager: customField.eager,
})(instance, name);
(0, typeorm_1.JoinColumn)()(instance, name);
}
}
else {
const options = {
type: getColumnType(dbEngine, customField.type, list !== null && list !== void 0 ? list : false),
default: getDefault(customField, dbEngine),
name,
nullable: nullable === false ? false : true,
unique: (_a = customField.unique) !== null && _a !== void 0 ? _a : false,
};
if ((customField.type === 'string' || customField.type === 'localeString') && !list) {
const length = customField.length || 255;
if (MAX_STRING_LENGTH < length) {
throw new Error(`ERROR: The "length" property of the custom field "${customField.name}" is ` +
`greater than the maximum allowed value of ${MAX_STRING_LENGTH}`);
}
options.length = length;
}
if (customField.type === 'float' &&
typeof customField.defaultValue === 'number' &&
(dbEngine === 'mariadb' || dbEngine === 'mysql')) {
// In the MySQL driver, a default float value will get rounded to the nearest integer.
// unless you specify the precision.
const defaultValueDecimalPlaces = customField.defaultValue.toString().split('.')[1];
if (defaultValueDecimalPlaces) {
options.scale = defaultValueDecimalPlaces.length;
}
}
if (customField.type === 'datetime' &&
options.precision == null &&
// Setting precision on an sqlite datetime will cause
// spurious migration commands. See https://github.com/typeorm/typeorm/issues/2333
dbEngine !== 'sqljs' &&
dbEngine !== 'sqlite' &&
!list) {
options.precision = 6;
}
(0, typeorm_1.Column)(options)(instance, name);
if ((dbEngine === 'mysql' || dbEngine === 'mariadb') && customField.unique === true) {
// The MySQL driver seems to work differently and will only apply a unique
// constraint if an index is defined on the column. For postgres/sqlite it is
// sufficient to add the `unique: true` property to the column options.
(0, typeorm_1.Index)({ unique: true })(instance, name);
}
}
};
if (translation) {
if (customField.type === 'localeString' || customField.type === 'localeText') {
registerColumn();
}
}
else {
if (customField.type !== 'localeString' && customField.type !== 'localeText') {
registerColumn();
}
}
const relationFieldsCount = customFields.filter(f => f.type === 'relation').length;
const nonLocaleStringFieldsCount = customFields.filter(f => f.type !== 'localeString' && f.type !== 'localeText' && f.type !== 'relation').length;
if (0 < relationFieldsCount && nonLocaleStringFieldsCount === 0) {
// if (customFields.filter(f => f.type === 'relation').length === customFields.length) {
// If there are _only_ relational customFields defined for an Entity, then TypeORM
// errors when attempting to load that entity ("Cannot set property <fieldName> of undefined").
// Therefore as a work-around we will add a "fake" column to the customFields embedded type
// to prevent this error from occurring.
(0, typeorm_1.Column)({
type: 'boolean',
nullable: true,
comment: 'A work-around needed when only relational custom fields are defined on an entity',
})(instance, '__fix_relational_custom_fields__');
}
}
}
}
function formatDefaultDatetime(dbEngine, datetime) {
if (!datetime) {
return datetime;
}
switch (dbEngine) {
case 'sqlite':
case 'sqljs':
return DateUtils_1.DateUtils.mixedDateToUtcDatetimeString(datetime);
case 'mysql':
case 'postgres':
default:
return DateUtils_1.DateUtils.mixedDateToUtcDatetimeString(datetime);
// return DateUtils.mixedDateToDate(datetime, true, true);
}
}
function getColumnType(dbEngine, type, isList) {
if (isList && type !== 'struct') {
return 'simple-json';
}
switch (type) {
case 'string':
case 'localeString':
return 'varchar';
case 'text':
case 'localeText':
switch (dbEngine) {
case 'mysql':
case 'mariadb':
return 'longtext';
default:
return 'text';
}
case 'boolean':
switch (dbEngine) {
case 'mysql':
return 'tinyint';
case 'postgres':
return 'bool';
case 'sqlite':
case 'sqljs':
default:
return 'boolean';
}
case 'int':
return 'int';
case 'float':
return 'double precision';
case 'datetime':
switch (dbEngine) {
case 'postgres':
return 'timestamp';
case 'mysql':
case 'sqlite':
case 'sqljs':
default:
return 'datetime';
}
case 'struct':
switch (dbEngine) {
case 'postgres':
return 'jsonb';
case 'mysql':
case 'mariadb':
return 'json';
case 'sqlite':
case 'sqljs':
default:
return 'simple-json';
}
default:
(0, shared_utils_1.assertNever)(type);
}
return 'varchar';
}
function getDefault(customField, dbEngine) {
const { name, type, list, defaultValue, nullable } = customField;
if (list && defaultValue) {
if (dbEngine === 'mysql') {
// MySQL does not support defaults on TEXT fields, which is what "simple-json" uses
// internally. See https://stackoverflow.com/q/3466872/772859
vendure_logger_1.Logger.warn(`MySQL does not support default values on list fields (${name}). No default will be set.`);
return undefined;
}
return JSON.stringify(defaultValue);
}
return type === 'datetime' ? formatDefaultDatetime(dbEngine, defaultValue) : defaultValue;
}
function assertLocaleFieldsNotSpecified(config, entityName) {
const customFields = config.customFields && config.customFields[entityName];
if (customFields) {
for (const customField of customFields) {
if (customField.type === 'localeString' || customField.type === 'localeText') {
vendure_logger_1.Logger.error(`Custom field "${customField.name}" on entity "${entityName}" cannot be of type "localeString" or "localeText". ` +
`This entity does not support localization.`);
}
}
}
}
/**
* Dynamically registers any custom fields with TypeORM. This function should be run at the bootstrap
* stage of the app lifecycle, before the AppModule is initialized.
*/
function registerCustomEntityFields(config) {
var _a;
// In order to determine the classes used for the custom field embedded types, we need
// to introspect the metadata args storage.
const metadataArgsStorage = (0, typeorm_1.getMetadataArgsStorage)();
for (const [entityName, customFieldsConfig] of Object.entries((_a = config.customFields) !== null && _a !== void 0 ? _a : {})) {
if (customFieldsConfig && customFieldsConfig.length) {
const customFieldsMetadata = getCustomFieldsMetadata(entityName);
const customFieldsClass = customFieldsMetadata.type();
if (customFieldsClass && typeof customFieldsClass !== 'string') {
registerCustomFieldsForEntity(config, entityName, customFieldsClass);
}
const translationsMetadata = metadataArgsStorage
.filterRelations(customFieldsMetadata.target)
.find(m => m.propertyName === 'translations');
if (translationsMetadata) {
// This entity is translatable, which means that we should
// also register any localized custom fields on the related
// EntityTranslation entity.
const translationType = translationsMetadata.type();
const customFieldsTranslationsMetadata = getCustomFieldsMetadata(translationType);
const customFieldsTranslationClass = customFieldsTranslationsMetadata.type();
if (customFieldsTranslationClass && typeof customFieldsTranslationClass !== 'string') {
registerCustomFieldsForEntity(config, entityName, customFieldsTranslationClass, true);
}
}
else {
assertLocaleFieldsNotSpecified(config, entityName);
}
}
}
function getCustomFieldsMetadata(entity) {
const entityName = typeof entity === 'string' ? entity : entity.name;
const metadataArgs = metadataArgsStorage.embeddeds.find(item => {
if (item.propertyName === 'customFields') {
const targetName = typeof item.target === 'string' ? item.target : item.target.name;
return targetName === entityName;
}
});
if (!metadataArgs) {
throw new Error(`Could not find embedded CustomFields property on entity "${entityName}"`);
}
return metadataArgs;
}
}
//# sourceMappingURL=register-custom-entity-fields.js.map