breeze-sequelize
Version:
Breeze Sequelize server implementation
225 lines • 10.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const breeze_client_1 = require("breeze-client");
const fs_1 = require("fs");
const path_1 = require("path");
const sequelize_1 = require("sequelize");
const utils_1 = require("./utils");
/** Maps Sequelize model definitions to Breeze metadata */
class ModelMapper {
constructor(metadataStore) {
/** Map from Sequelize DataTypes.DataType to Breeze DataType*/
this.dataTypeMap = {
"STRING": breeze_client_1.DataType.String,
"CHAR": breeze_client_1.DataType.String,
"NCHAR": breeze_client_1.DataType.String,
"TEXT": breeze_client_1.DataType.String,
"NUMBER": breeze_client_1.DataType.Decimal,
"TINYINT": breeze_client_1.DataType.Byte,
"SMALLINT": breeze_client_1.DataType.Int16,
"MEDIUMINT": breeze_client_1.DataType.Int32,
"INTEGER": breeze_client_1.DataType.Int32,
"BIGINT": breeze_client_1.DataType.Int64,
"FLOAT": breeze_client_1.DataType.Single,
"REAL": breeze_client_1.DataType.Single,
"DOUBLE": breeze_client_1.DataType.Double,
"DECIMAL": breeze_client_1.DataType.Decimal,
"MONEY": breeze_client_1.DataType.Decimal,
"SMALLMONEY": breeze_client_1.DataType.Decimal,
"BOOLEAN": breeze_client_1.DataType.Boolean,
"TIME": breeze_client_1.DataType.Time,
"DATE": breeze_client_1.DataType.DateTime,
"DATEONLY": breeze_client_1.DataType.DateTime,
"HSTORE": breeze_client_1.DataType.Undefined,
"JSON": breeze_client_1.DataType.String,
"JSONB": breeze_client_1.DataType.String,
"NOW": breeze_client_1.DataType.DateTimeOffset,
"BLOB": breeze_client_1.DataType.Binary,
"LONGBLOB": breeze_client_1.DataType.Binary,
"IMAGE": breeze_client_1.DataType.Binary,
"VARBINARY": breeze_client_1.DataType.Binary,
"RANGE": breeze_client_1.DataType.Undefined,
"UUID": breeze_client_1.DataType.Guid,
"UUIDV1": breeze_client_1.DataType.Guid,
"UUIDV4": breeze_client_1.DataType.Guid,
"VIRTUAL": breeze_client_1.DataType.String,
"ENUM": breeze_client_1.DataType.String,
"ARRAY": breeze_client_1.DataType.Undefined,
"GEOMETRY": breeze_client_1.DataType.String,
"GEOGRAPHY": breeze_client_1.DataType.String,
"CIDR": breeze_client_1.DataType.String,
"INET": breeze_client_1.DataType.String,
"MACADDR": breeze_client_1.DataType.String,
"CITEXT": breeze_client_1.DataType.String,
};
this.metadataStore = metadataStore;
}
/** Load the sequelize instance with the model files from the directory */
static loadSequelizeModels(sequelize, modeldir) {
const initFile = path_1.join(modeldir, "init-models.js");
if (fs_1.existsSync(initFile)) {
// load models via initModels function
utils_1.log("loading", initFile);
const init = require(initFile);
init.initModels(sequelize);
}
else {
// load models by requiring each file
const files = fs_1.readdirSync(modeldir);
files.forEach(file => {
const modelpath = path_1.join(modeldir, file);
utils_1.log("loading", modelpath);
const mod = require(modelpath);
if (typeof mod === "function") {
mod(sequelize, sequelize_1.DataTypes);
}
});
}
}
/** Add all the Models in the Sequelize instance to the MetadataStore */
addModels(sequelize, namespace) {
const models = sequelize.models;
const modelNames = Object.keys(models);
modelNames.forEach(name => {
utils_1.log(`Adding ${name}`);
const modelCtor = sequelize.model(name);
this.addModel(modelCtor, namespace);
});
this.addInverseNavigations();
}
/** Convert the Sequelize Model to a Breeze EntityType and add it to the MetadataStore */
addModel(model, namespace) {
if (model.options.schema) {
namespace += "." + model.options.schema;
}
const pluralName = sequelize_1.Utils.pluralize(model.name);
const config = {
shortName: model.name,
namespace: namespace,
defaultResourceName: pluralName,
};
const et = new breeze_client_1.EntityType(config);
const attrNames = Object.keys(model.rawAttributes);
const version = model.options.version === true ? "version" : model.options.version;
attrNames.forEach(attrName => {
const attr = model.rawAttributes[attrName];
const dataType = this.mapDataType(attr.type);
if (!dataType || dataType === breeze_client_1.DataType.Undefined) {
utils_1.log(`Sequelize data type ${attr.type} is not supported. Model '${model.name}', attr '${attrName}'.`);
}
const dp = new breeze_client_1.DataProperty({
nameOnServer: attrName,
isNullable: attr.allowNull,
isPartOfKey: attr.primaryKey,
dataType: dataType,
defaultValue: attr.defaultValue,
});
if (version && attrName === version) {
dp.concurrencyMode = "Fixed";
}
const maxLength = this.getLength(attr.type);
if (maxLength) {
dp.maxLength = maxLength;
}
if (attr.primaryKey && attr.autoIncrement) {
// Guessing about Identity vs KeyGenerator
et.autoGeneratedKeyType = dataType.isInteger ? breeze_client_1.AutoGeneratedKeyType.Identity : breeze_client_1.AutoGeneratedKeyType.KeyGenerator;
}
if (attr.references) {
// attr is a foreign key
const ref = attr.references;
// refName is name of referenced entity, e.g. customer
const refName = (typeof ref.model === "string") ? ref.model : ref.model.name;
// use the id property name to make the navigation property name
let propName;
if (attrName.toLowerCase().endsWith('id')) {
propName = attrName.substring(0, attrName.length - 2);
}
else {
propName = this.matchCase(refName, attrName);
// if there's an existing property, add fk name to property name to make it unique
if (et.getNavigationProperty(propName)) {
propName = attr.field + '_' + propName;
}
}
const np = new breeze_client_1.NavigationProperty({
associationName: (model.name + '_' + propName + '_' + attrName).toLocaleLowerCase(),
entityTypeName: refName,
foreignKeyNamesOnServer: [attrName],
isScalar: true,
nameOnServer: propName
});
et.addProperty(np);
}
et.addProperty(dp);
});
this.metadataStore.addEntityType(et);
}
/** Create non-scalar navigation properties on the inverse side of the scalar properties,
* for all EntityTypes in the MetadataStore.
*/
addInverseNavigations() {
const ms = this.metadataStore;
const entityTypes = this.metadataStore.getEntityTypes().filter(t => !t.isComplexType);
entityTypes.forEach(entityType => {
const navs = entityType.getProperties().filter(p => p.isNavigationProperty && p.isScalar);
navs.forEach(nav => {
// nav is e.g. Order.Customer property
// inverseName is e.g. "Orders"
let inverseName = this.matchCase(sequelize_1.Utils.pluralize(entityType.shortName), nav.nameOnServer);
// navType is e.g. Customer
const navType = ms.getAsEntityType(nav.entityTypeName);
// inverseNav is e.g. Customer.Orders
const inverseNav = navType.getNavigationProperty(inverseName);
if (inverseNav == null || inverseNav.associationName !== nav.associationName) {
if (inverseNav != null) {
// already a property with this name, so add uniquifier e.g. Customer_Orders
inverseName = nav.name + '_' + inverseName;
}
const np = new breeze_client_1.NavigationProperty({
associationName: nav.associationName,
entityTypeName: entityType.name,
invForeignKeyNames: nav.foreignKeyNames,
isScalar: false,
nameOnServer: inverseName
});
navType.addProperty(np);
}
});
});
}
/** Make the first char of s match the case of the first char of the target string */
matchCase(s, target) {
if (/^[A-Z]/.test(target)) {
s = s[0].toUpperCase() + s.substring(1);
}
else {
s = s[0].toLowerCase() + s.substring(1);
}
return s;
}
/** Return the Breeze DataType for the given Sequelize DataType */
mapDataType(sqDataType) {
let name = (typeof sqDataType === "string") ? sqDataType : sqDataType.key;
let type = this.dataTypeMap[name];
if (!type) {
// if type is e.g. NCHAR(5), try without the (5)
const p = name.indexOf('(');
if (p > 0) {
name = name.substring(0, p);
type = this.dataTypeMap[name];
}
}
return type;
}
/** For string data types, return the length, else undefined */
getLength(sqDataType) {
if (typeof sqDataType === "string" || (sqDataType.key !== "STRING" && sqDataType.key !== "CHAR")) {
return undefined;
}
const stringType = sqDataType;
return stringType.options && stringType.options.length;
}
}
exports.ModelMapper = ModelMapper;
//# sourceMappingURL=ModelMapper.js.map