breeze-sequelize
Advanced tools
Comparing version 0.3.0 to 0.4.0
@@ -1,40 +0,40 @@ | ||
var Sequelize = require('sequelize'); | ||
var Promise = require('bluebird'); | ||
var utils = require('./utils.js'); | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var sequelize_1 = require("sequelize"); | ||
var utils = require('./utils.js'); | ||
var log = utils.log; | ||
exports.connect = connect; | ||
exports.createDb = createDb; | ||
// returns a Promise(connection) | ||
/** @returns Promise<"success"> or throws an error */ | ||
function connect(dbConfig, sequelizeOptions) { | ||
var sequelize = new Sequelize(dbConfig.dbName, dbConfig.user, dbConfig.password, sequelizeOptions); | ||
var statement = 'SELECT 1'; | ||
return sequelize.query(statement, { type: sequelize.QueryTypes.RAW}).then(function(results) { | ||
log("Connected to database: " + dbConfig.dbName); | ||
return "success"; | ||
}).error(function(err) { | ||
log("Database error: " + dbConfig.dbName + " error: " + err.message); | ||
throw err; | ||
}); | ||
}; | ||
// return promise(null); | ||
var sequelize = new sequelize_1.Sequelize(dbConfig.dbName, dbConfig.user, dbConfig.password, sequelizeOptions); | ||
var statement = 'SELECT 1'; | ||
return sequelize.query(statement, { type: sequelize_1.QueryTypes.RAW }).then(function (results) { | ||
log("Connected to database: " + dbConfig.dbName); | ||
return "success"; | ||
}).error(function (err) { | ||
log("Database error: " + dbConfig.dbName + " error: " + err.message); | ||
throw err; | ||
}); | ||
} | ||
exports.connect = connect; | ||
; | ||
/** @returns Promise<void> or throws an error */ | ||
function createDb(dbConfig, sequelizeOptions) { | ||
var sequelize = new Sequelize(null, dbConfig.user, dbConfig.password, sequelizeOptions); | ||
var statement = 'CREATE DATABASE ' + dbConfig.dbName; | ||
return sequelize.query(statement, { type: sequelize.QueryTypes.RAW}).then(function() { | ||
log("Database created: " + dbConfig.dbName); | ||
}).error(function(err) { | ||
if (err.message && err.message.indexOf("ER_DB_CREATE_EXISTS") >= 0) { | ||
log("Database already exists: " + dbConfig.dbName); | ||
} else { | ||
log("Database creation error: " + dbConfig.dbName + " error: " + err.message); | ||
throw err; | ||
} | ||
}); | ||
}; | ||
var sequelize = new sequelize_1.Sequelize(null, dbConfig.user, dbConfig.password, sequelizeOptions); | ||
var statement = 'CREATE DATABASE ' + dbConfig.dbName; | ||
return sequelize.query(statement, { type: sequelize_1.QueryTypes.RAW }).then(function () { | ||
log("Database created: " + dbConfig.dbName); | ||
}).error(function (err) { | ||
if (err.message && err.message.indexOf("ER_DB_CREATE_EXISTS") >= 0) { | ||
log("Database already exists: " + dbConfig.dbName); | ||
} | ||
else { | ||
log("Database creation error: " + dbConfig.dbName + " error: " + err.message); | ||
throw err; | ||
} | ||
}); | ||
} | ||
exports.createDb = createDb; | ||
; | ||
// old version using node 'next' semantics. | ||
@@ -56,2 +56,2 @@ // next => function(err, connection); | ||
//} | ||
//# sourceMappingURL=dbUtils.js.map |
27
main.js
@@ -1,10 +0,17 @@ | ||
exports.SequelizeQuery = require("./SequelizeQuery.json.js"); | ||
exports.SequelizeManager = require("./SequelizeManager.js"); | ||
exports.SequelizeSaveHandler = require("./SequelizeSaveHandler.js"); | ||
exports.utils = require("./utils.js"); | ||
exports.dbUtils = require("./dbUtils"); | ||
exports.breeze = require("breeze-client"); // needed because we have augmented breeze in the SequelizeQuery component | ||
exports.Sequelize = exports.SequelizeManager.Sequelize; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var SequelizeQuery_1 = require("./SequelizeQuery"); | ||
exports.SequelizeQuery = SequelizeQuery_1.SequelizeQuery; | ||
var SequelizeManager_1 = require("./SequelizeManager"); | ||
exports.SequelizeManager = SequelizeManager_1.SequelizeManager; | ||
var SequelizeSaveHandler_1 = require("./SequelizeSaveHandler"); | ||
exports.SequelizeSaveHandler = SequelizeSaveHandler_1.SequelizeSaveHandler; | ||
var utils = require("./utils"); | ||
exports.utils = utils; | ||
var dbUtils = require("./dbUtils"); | ||
exports.dbUtils = dbUtils; | ||
var breeze_client_1 = require("breeze-client"); | ||
exports.breeze = breeze_client_1.breeze; | ||
var Sequelize = SequelizeManager_1.SequelizeManager.Sequelize; | ||
exports.Sequelize = Sequelize; | ||
//# sourceMappingURL=main.js.map |
@@ -1,161 +0,158 @@ | ||
var Sequelize = require('sequelize'); | ||
var _ = require('lodash'); | ||
var breeze = require("breeze-client"); | ||
var utils = require('./utils.js'); | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var breeze_client_1 = require("breeze-client"); | ||
var sequelize_1 = require("sequelize"); | ||
var _ = require("lodash"); | ||
var utils = require("./utils"); | ||
var log = utils.log; | ||
; | ||
// TODO: still need to handle inherited entity types - TPT | ||
module.exports = MetadataMapper = function(breezeMetadata, sequelize) { | ||
this.sequelize = sequelize; | ||
var ms; | ||
if (breezeMetadata instanceof breeze.MetadataStore) { | ||
ms = breezeMetadata; | ||
} else { | ||
var ms = new breeze.MetadataStore(); | ||
ms.importMetadata(breezeMetadata); | ||
} | ||
this.metadataStore = ms; | ||
this._createMaps(); | ||
} | ||
MetadataMapper.prototype._createMaps = function() { | ||
// creates entityTypeSqModelMap and resourceNameSqModelMap | ||
var ms = this.metadataStore; | ||
var allTypes = ms.getEntityTypes(); | ||
var typeMap = _.groupBy(allTypes, function(t) { | ||
return t.isComplexType ? "complexType" : "entityType"; | ||
}); | ||
var complexTypes = typeMap["complexType"]; | ||
var entityTypes = typeMap["entityType"]; | ||
// map of entityTypeName to sqModel | ||
var entityTypeSqModelMap = this.entityTypeSqModelMap = {}; | ||
// first create all of the sequelize types with just data properties | ||
entityTypes.forEach(function(entityType) { | ||
var typeConfig = mapToSqModelConfig(this, entityType); | ||
var options = { | ||
// NOTE: case sensitivity of the table name may not be the same on some sql databases. | ||
modelName: entityType.shortName, // this will define the table's name; see options.define | ||
/** Maps Breeze metadata to Sequelize Models */ | ||
var MetadataMapper = /** @class */ (function () { | ||
function MetadataMapper(breezeMetadata, sequelize) { | ||
this.sequelize = sequelize; | ||
var ms; | ||
if (breezeMetadata instanceof breeze_client_1.MetadataStore) { | ||
ms = breezeMetadata; | ||
} | ||
else { | ||
ms = new breeze_client_1.breeze.MetadataStore(); | ||
ms.importMetadata(breezeMetadata); | ||
} | ||
this.metadataStore = ms; | ||
this._createMaps(); | ||
} | ||
/** creates entityTypeSqModelMap and resourceNameSqModelMap */ | ||
MetadataMapper.prototype._createMaps = function () { | ||
var _this = this; | ||
var ms = this.metadataStore; | ||
var allTypes = ms.getEntityTypes(); | ||
var typeMap = _.groupBy(allTypes, function (t) { | ||
return t.isComplexType ? "complexType" : "entityType"; | ||
}); | ||
// let complexTypes = typeMap["complexType"]; | ||
var entityTypes = typeMap["entityType"]; | ||
// map of entityTypeName to sqModel | ||
var entityTypeSqModelMap = this.entityTypeSqModelMap = {}; | ||
// first create all of the sequelize types with just data properties | ||
entityTypes.forEach(function (entityType) { | ||
var typeConfig = _this.mapToSqModelConfig(entityType); | ||
var options = { | ||
// NOTE: case sensitivity of the table name may not be the same on some sql databases. | ||
modelName: entityType.shortName, | ||
}; | ||
var sqModel = _this.sequelize.define(entityType.shortName, typeConfig, options); | ||
entityTypeSqModelMap[entityType.name] = sqModel; | ||
}, this); | ||
// now add navigation props | ||
this.createNavProps(entityTypes, entityTypeSqModelMap); | ||
// map of breeze resourceName to sequelize model | ||
this.resourceNameSqModelMap = _.mapValues(ms._resourceEntityTypeMap, function (value, key) { | ||
return entityTypeSqModelMap[value]; | ||
}); | ||
}; | ||
var sqModel = this.sequelize.define(entityType.shortName, typeConfig, options); | ||
entityTypeSqModelMap[entityType.name] = sqModel; | ||
}, this); | ||
// now add navigation props | ||
createNavProps(entityTypes, entityTypeSqModelMap); | ||
// map of breeze resourceName to sequelize model | ||
this.resourceNameSqModelMap = _.mapValues(ms._resourceEntityTypeMap, function(value, key) { | ||
return entityTypeSqModelMap[value]; | ||
}); | ||
}; | ||
// source.fn(target, { foreignKey: }) | ||
// hasOne - adds a foreign key to target | ||
// belongsTo - add a foreign key to source | ||
// hasMany - adds a foreign key to target, unless you also specifiy that target hasMany source, in which case a junction table is created with sourceId and targetId | ||
// entityTypeMap is a map of entityType.name to sequelize model | ||
function createNavProps(entityTypes, entityTypeSqModelMap) { | ||
// TODO: we only support single column foreignKeys for now. | ||
entityTypes.forEach(function(entityType) { | ||
var navProps = entityType.navigationProperties; | ||
var sqModel = entityTypeSqModelMap[entityType.name]; | ||
navProps.forEach(function(np) { | ||
var npName = np.nameOnServer; | ||
var targetEntityType = np.entityType; | ||
var targetSqModel = entityTypeSqModelMap[targetEntityType.name]; | ||
if (np.isScalar) { | ||
if (np.foreignKeyNamesOnServer.length > 0) { | ||
sqModel.belongsTo(targetSqModel, { as: npName, foreignKey: np.foreignKeyNamesOnServer[0], onDelete: "no action" }); // Product, Category | ||
} else { | ||
sqModel.hasOne(targetSqModel, { as: npName, foreignKey: np.invForeignKeyNamesOnServer[0], onDelete: "no action" }); // Order, InternationalOrder | ||
// source.fn(target, { foreignKey: }) | ||
// hasOne - adds a foreign key to target | ||
// belongsTo - add a foreign key to source | ||
// hasMany - adds a foreign key to target, unless you also specifiy that target hasMany source, in which case a junction table is created with sourceId and targetId | ||
/** Adds relationships to the Models based on Breeze NavigationProperties */ | ||
MetadataMapper.prototype.createNavProps = function (entityTypes, entityTypeSqModelMap) { | ||
// TODO: we only support single column foreignKeys for now. | ||
entityTypes.forEach(function (entityType) { | ||
var navProps = entityType.navigationProperties; | ||
var sqModel = entityTypeSqModelMap[entityType.name]; | ||
navProps.forEach(function (np) { | ||
var npName = np.nameOnServer; | ||
var targetEntityType = np.entityType; | ||
var targetSqModel = entityTypeSqModelMap[targetEntityType.name]; | ||
if (np.isScalar) { | ||
if (np.foreignKeyNamesOnServer.length > 0) { | ||
sqModel.belongsTo(targetSqModel, { as: npName, foreignKey: np.foreignKeyNamesOnServer[0], onDelete: "no action" }); // Product, Category | ||
} | ||
else { | ||
sqModel.hasOne(targetSqModel, { as: npName, foreignKey: np.invForeignKeyNamesOnServer[0], onDelete: "no action" }); // Order, InternationalOrder | ||
} | ||
} | ||
else { | ||
if (np.foreignKeyNamesOnServer.length > 0) { | ||
throw new Error("not sure what kind of reln this is"); | ||
// sqModel.hasMany(targetSqModel, { as: npName, foreignKey: np.foreignKeyNamesOnServer[0]}) | ||
} | ||
else { | ||
sqModel.hasMany(targetSqModel, { as: npName, foreignKey: np.invForeignKeyNamesOnServer[0], onDelete: "no action" }); // Category, Product | ||
} | ||
} | ||
}); | ||
}); | ||
}; | ||
/** Creates a set of Sequelize attributes based on DataProperties */ | ||
MetadataMapper.prototype.mapToSqModelConfig = function (entityOrComplexType) { | ||
// propConfig looks like | ||
// { firstProp: { type: Sequelize.XXX, ... }, | ||
// secondProp: { type: Sequelize.XXX, ... } | ||
// .. | ||
// } | ||
var _this = this; | ||
var typeConfig = {}; | ||
entityOrComplexType.dataProperties.forEach(function (dataProperty) { | ||
var propConfig = _this.mapToSqPropConfig(dataProperty); | ||
_.merge(typeConfig, propConfig); | ||
}); | ||
return typeConfig; | ||
}; | ||
/** Creates Sequelize column attributes based on a DataProperty */ | ||
MetadataMapper.prototype.mapToSqPropConfig = function (dataProperty) { | ||
if (dataProperty.isComplexProperty) { | ||
return this.mapToSqModelConfig(dataProperty.dataType); | ||
} | ||
} else { | ||
if ( np.foreignKeyNamesOnServer.length > 0) { | ||
throw new Error("not sure what kind of reln this is"); | ||
// sqModel.hasMany(targetSqModel, { as: npName, foreignKey: np.foreignKeyNamesOnServer[0]}) | ||
} else { | ||
sqModel.hasMany(targetSqModel, { as: npName, foreignKey: np.invForeignKeyNamesOnServer[0], onDelete: "no action"}) // Category, Product | ||
var propConfig = {}; | ||
var attributes = {}; | ||
propConfig[dataProperty.nameOnServer] = attributes; | ||
var sqModel = _dataTypeMap[dataProperty.dataType.name]; | ||
if (sqModel == null) { | ||
var template = _.template("Unable to map the dataType '${ dataType }' of dataProperty: '${ dataProperty }'"); | ||
throw new Error(template({ dataProperty: dataProperty.parentType.shortName + "." + dataProperty.name, dataType: dataProperty.dataType.name })); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
function mapToSqModelConfig(mapper, entityOrComplexType) { | ||
// propConfig looks like | ||
// { firstProp: { type: Sequelize.XXX, ... }, | ||
// secondProp: { type: Sequelize.XXX, ... } | ||
// .. | ||
// } | ||
var typeConfig = {}; | ||
entityOrComplexType.dataProperties.forEach(function(dataProperty) { | ||
var propConfig = mapToSqPropConfig(mapper, dataProperty); | ||
_.merge(typeConfig, propConfig); | ||
}); | ||
return typeConfig; | ||
} | ||
function mapToSqPropConfig(mapper, dataProperty) { | ||
if (dataProperty.isComplexProperty) { | ||
return mapToSqModelConfig(mapper, dataProperty.dataType); | ||
} | ||
var propConfig = {}; | ||
var attributes = {}; | ||
propConfig[dataProperty.nameOnServer] = attributes; | ||
var sqModel = _dataTypeMap[dataProperty.dataType.name]; | ||
if (sqModel == null) { | ||
var template = _.template("Unable to map the dataType '${ dataType }' of dataProperty: '${ dataProperty }'"); | ||
throw new Error( template({ dataProperty: dataProperty.parentType.shortName + "." + dataProperty.name, dataType: dataProperty.dataType.name })); | ||
} | ||
attributes.type = sqModel; | ||
if (dataProperty.dataType == breeze.DataType.String && dataProperty.maxLength) { | ||
attributes.type = Sequelize.STRING(dataProperty.maxLength); | ||
} | ||
if (!dataProperty.isNullable) { | ||
attributes.allowNull = false; | ||
} | ||
if (dataProperty.isPartOfKey) { | ||
attributes.primaryKey = true; | ||
if (dataProperty.parentType.autoGeneratedKeyType == breeze.AutoGeneratedKeyType.Identity) { | ||
if (attributes.type.key == "INTEGER" || attributes.type.key == "BIGINT") { | ||
attributes.autoIncrement = true; | ||
} | ||
} | ||
} | ||
if (dataProperty.defaultValue !== undefined && !dataProperty.isPartOfKey) { | ||
// if (dataProperty.defaultValue !== undefined) { | ||
attributes.defaultValue = dataProperty.defaultValue; | ||
} | ||
return propConfig; | ||
} | ||
attributes.type = sqModel; | ||
if (dataProperty.dataType == breeze_client_1.breeze.DataType.String && dataProperty.maxLength) { | ||
attributes.type = sequelize_1.DataTypes.STRING(dataProperty.maxLength); | ||
} | ||
if (!dataProperty.isNullable) { | ||
attributes.allowNull = false; | ||
} | ||
if (dataProperty.isPartOfKey) { | ||
attributes.primaryKey = true; | ||
if (dataProperty.parentType.autoGeneratedKeyType == breeze_client_1.breeze.AutoGeneratedKeyType.Identity) { | ||
var dt = attributes.type; | ||
if (dt.key == "INTEGER" || dt.key == "BIGINT") { | ||
attributes.autoIncrement = true; | ||
} | ||
} | ||
} | ||
if (dataProperty.defaultValue !== undefined && !dataProperty.isPartOfKey) { | ||
// if (dataProperty.defaultValue !== undefined) { | ||
attributes.defaultValue = dataProperty.defaultValue; | ||
} | ||
return propConfig; | ||
}; | ||
return MetadataMapper; | ||
}()); | ||
exports.MetadataMapper = MetadataMapper; | ||
var _dataTypeMap = { | ||
String: Sequelize.STRING, | ||
Boolean: Sequelize.BOOLEAN, | ||
DateTime: Sequelize.DATE, | ||
DateTimeOffset: Sequelize.DATE, | ||
Byte: Sequelize.INTEGER.UNSIGNED, | ||
Int16: Sequelize.INTEGER, | ||
Int32: Sequelize.INTEGER, | ||
Int64: Sequelize.BIGINT, | ||
Decimal: Sequelize.DECIMAL(19,4), | ||
Double: Sequelize.FLOAT, | ||
Single: Sequelize.FLOAT, | ||
Guid: Sequelize.UUID, | ||
Binary: Sequelize.STRING.BINARY, | ||
Time: Sequelize.STRING, | ||
Undefined: Sequelize.BLOB | ||
}; | ||
String: sequelize_1.DataTypes.STRING, | ||
Boolean: sequelize_1.DataTypes.BOOLEAN, | ||
DateTime: sequelize_1.DataTypes.DATE, | ||
DateTimeOffset: sequelize_1.DataTypes.DATE, | ||
Byte: sequelize_1.DataTypes.INTEGER.UNSIGNED, | ||
Int16: sequelize_1.DataTypes.INTEGER, | ||
Int32: sequelize_1.DataTypes.INTEGER, | ||
Int64: sequelize_1.DataTypes.BIGINT, | ||
Decimal: sequelize_1.DataTypes.DECIMAL(19, 4), | ||
Double: sequelize_1.DataTypes.FLOAT, | ||
Single: sequelize_1.DataTypes.FLOAT, | ||
Guid: sequelize_1.DataTypes.UUID, | ||
Binary: sequelize_1.DataTypes.STRING().BINARY, | ||
Time: sequelize_1.DataTypes.STRING, | ||
Undefined: sequelize_1.DataTypes.BLOB | ||
}; | ||
//# sourceMappingURL=MetadataMapper.js.map |
{ | ||
"name": "breeze-sequelize", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Breeze Sequelize server implementation", | ||
@@ -10,2 +10,3 @@ "keywords": [ | ||
"query", | ||
"relational", | ||
"linq", | ||
@@ -15,16 +16,29 @@ "graph" | ||
"main": "main.js", | ||
"types": "types/main.d.ts", | ||
"files": [ | ||
"*.js", | ||
"types/*" | ||
], | ||
"directories": {}, | ||
"dependencies": { | ||
"bluebird": "^3.5.5", | ||
"breeze-client": "^1.7.2", | ||
"bluebird": "^3.7.2", | ||
"breeze-client": ">=2.0.3", | ||
"lodash": "^4.17.15", | ||
"sequelize": "^5.18.1", | ||
"sequelize": "^5.21.3", | ||
"toposort": "^2.0.2" | ||
}, | ||
"devDependencies": { | ||
"@types/bluebird": "^3.5.29", | ||
"@types/lodash": "^4.14.137", | ||
"@types/node": "^12.12.25", | ||
"@types/validator": "^12.0.1", | ||
"chai": "^4.2.0", | ||
"mocha": "^6.2.0" | ||
"mocha": "^6.2.0", | ||
"rimraf": "^3.0.0", | ||
"typescript": "~3.4.5" | ||
}, | ||
"scripts": { | ||
"test": "mocha" | ||
"test": "mocha", | ||
"build": "tsc && npm pack", | ||
"clean": "rimraf *.js && rimraf *.map && rimraf types" | ||
}, | ||
@@ -31,0 +45,0 @@ "repository": { |
@@ -1,5 +0,12 @@ | ||
# The "breeze-sequelize" npm package | ||
# breeze-sequelize | ||
This is the official NPM package for the Breeze Sequelize integration. The package files are in the [breeze.server.node](https://github.com/Breeze/breeze.server.node "github: "breeze-server-node") repository in the 'breeze-sequelize' subfolder. | ||
The `breeze-sequelize` library lets you easily build a Sequelize server for managing relational data. | ||
Starting with Breeze metadata, it will create the Sequelize model for you, and Sequelize can create a database from the model. | ||
Once you have the model and database, `breeze-sequelize` makes it easy to query and update data from your Breeze client. | ||
## Install | ||
To install with npm, open a terminal or command window and enter: | ||
@@ -6,0 +13,0 @@ |
@@ -1,86 +0,78 @@ | ||
var Sequelize = require('sequelize'); | ||
var breeze = require("breeze-client"); | ||
var Promise = require("bluebird"); | ||
var _ = require('lodash'); | ||
var MetadataMapper = require('./MetadataMapper.js'); | ||
var dbUtils = require('./dbUtils.js'); | ||
var utils = require('./utils.js'); | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var sequelize_1 = require("sequelize"); | ||
var dbUtils_1 = require("./dbUtils"); | ||
var MetadataMapper_1 = require("./MetadataMapper"); | ||
var _ = require("lodash"); | ||
var utils = require("./utils"); | ||
var log = utils.log; | ||
module.exports = SequelizeManager; | ||
function SequelizeManager(dbConfig, sequelizeOptions) { | ||
var defaultOptions = { | ||
dialect: "mysql", // or 'sqlite', 'postgres', 'mariadb' | ||
port: 3306, // or 5432 (for postgres) | ||
// omitNull: true, | ||
logging: console.log, | ||
dialectOptions: { decimalNumbers: true }, | ||
define: { | ||
freezeTableName: true, // prevent sequelize from pluralizing table names | ||
timestamps: false // deactivate the timestamp columns (createdAt, etc.) | ||
} | ||
}; | ||
var define = defaultOptions.define; | ||
this.sequelizeOptions = _.extend(defaultOptions, sequelizeOptions || {}); | ||
this.sequelizeOptions.define = _.extend(define, (sequelizeOptions && sequelizeOptions.define) || {}); | ||
this.dbConfig = dbConfig; | ||
this.sequelize = new Sequelize(dbConfig.dbName, dbConfig.user, dbConfig.password, this.sequelizeOptions); | ||
} | ||
// Expose Sequelize from outside | ||
SequelizeManager.Sequelize = Sequelize; | ||
// returns Promise(null); | ||
SequelizeManager.prototype.authenticate = function() { | ||
// check database connection | ||
return this.sequelize.authenticate().then(function() { | ||
log('Connection has been established successfully.'); | ||
}).error(function(err) { | ||
log('Unable to connect to the database:', err); | ||
throw err; | ||
}); | ||
}; | ||
SequelizeManager.prototype.createDb = function() { | ||
return dbUtils.createDb(this.dbConfig, this.sequelizeOptions); | ||
}; | ||
SequelizeManager.prototype.importMetadata = function(breezeMetadata) { | ||
var metadataMapper = new MetadataMapper(breezeMetadata, this.sequelize); | ||
// TODO: should we merge here instead ; i.e. allow multiple imports... | ||
this.models = this.resourceNameSqModelMap = metadataMapper.resourceNameSqModelMap; | ||
this.entityTypeSqModelMap = metadataMapper.entityTypeSqModelMap; | ||
this.metadataStore = metadataMapper.metadataStore; | ||
}; | ||
// returns Promise(sequelize); | ||
SequelizeManager.prototype.sync = function(shouldCreateDb, sequelizeOpts) { | ||
if (shouldCreateDb) { | ||
var that = this; | ||
return this.createDb().then(function() { | ||
return syncCore(that.sequelize, sequelizeOpts); | ||
}); | ||
} else { | ||
return syncCore(this.sequelize, sequelizeOpts); | ||
} | ||
}; | ||
// returns Promise(sequelize); | ||
function syncCore(sequelize, sequelizeOpts) { | ||
var defaultOptions = { force: true }; | ||
sequelizeOpts = _.extend(defaultOptions, sequelizeOpts || {}); | ||
return sequelize.sync(sequelizeOpts).then(function() { | ||
log("schema created"); | ||
return sequelize; | ||
}).catch(function(err) { | ||
console.log("schema creation failed"); | ||
throw err; | ||
}); | ||
} | ||
/** Manages the Sequelize instance for Breeze query and save operations */ | ||
var SequelizeManager = /** @class */ (function () { | ||
function SequelizeManager(dbConfig, sequelizeOptions) { | ||
var defaultOptions = { | ||
dialect: "mysql", | ||
port: 3306, | ||
// omitNull: true, | ||
logging: console.log, | ||
dialectOptions: { decimalNumbers: true }, | ||
define: { | ||
freezeTableName: true, | ||
timestamps: false // deactivate the timestamp columns (createdAt, etc.) | ||
} | ||
}; | ||
var define = defaultOptions.define; | ||
this.sequelizeOptions = _.extend(defaultOptions, sequelizeOptions || {}); | ||
this.sequelizeOptions.define = _.extend(define, (sequelizeOptions && sequelizeOptions.define) || {}); | ||
this.dbConfig = dbConfig; | ||
this.sequelize = new sequelize_1.Sequelize(dbConfig.dbName, dbConfig.user, dbConfig.password, this.sequelizeOptions); | ||
} | ||
/** Connect to the database */ | ||
SequelizeManager.prototype.authenticate = function () { | ||
// check database connection | ||
return this.sequelize.authenticate().then(function () { | ||
log('Connection has been established successfully.'); | ||
}).error(function (err) { | ||
log('Unable to connect to the database:', err); | ||
throw err; | ||
}); | ||
}; | ||
/** Create a new database */ | ||
SequelizeManager.prototype.createDb = function () { | ||
return dbUtils_1.createDb(this.dbConfig, this.sequelizeOptions); | ||
}; | ||
/** Convert Breeze metadata to Sequelize models */ | ||
SequelizeManager.prototype.importMetadata = function (breezeMetadata) { | ||
var metadataMapper = new MetadataMapper_1.MetadataMapper(breezeMetadata, this.sequelize); | ||
// TODO: should we merge here instead ; i.e. allow multiple imports... | ||
this.models = this.resourceNameSqModelMap = metadataMapper.resourceNameSqModelMap; | ||
this.entityTypeSqModelMap = metadataMapper.entityTypeSqModelMap; | ||
this.metadataStore = metadataMapper.metadataStore; | ||
}; | ||
/** Sync the Sequelize model with the database */ | ||
SequelizeManager.prototype.sync = function (shouldCreateDb, sequelizeOpts) { | ||
var _this = this; | ||
if (shouldCreateDb) { | ||
return this.createDb().then(function () { | ||
return _this.syncCore(_this.sequelize, sequelizeOpts); | ||
}); | ||
} | ||
else { | ||
return this.syncCore(this.sequelize, sequelizeOpts); | ||
} | ||
}; | ||
SequelizeManager.prototype.syncCore = function (sequelize, sequelizeOpts) { | ||
var defaultOptions = { force: true }; | ||
sequelizeOpts = _.extend(defaultOptions, sequelizeOpts || {}); | ||
return sequelize.sync(sequelizeOpts).then(function () { | ||
log("schema created"); | ||
return sequelize; | ||
}).catch(function (err) { | ||
console.log("schema creation failed"); | ||
throw err; | ||
}); | ||
}; | ||
SequelizeManager.Sequelize = sequelize_1.Sequelize; | ||
return SequelizeManager; | ||
}()); | ||
exports.SequelizeManager = SequelizeManager; | ||
//# sourceMappingURL=SequelizeManager.js.map |
@@ -1,530 +0,402 @@ | ||
var Sequelize = require('sequelize'); | ||
var Promise = require("bluebird"); | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var bluebird_1 = require("bluebird"); | ||
var _ = require("lodash"); | ||
var SaveMap_1 = require("./SaveMap"); | ||
var toposort = require("toposort"); | ||
var _ = require('lodash'); | ||
// TODO: transactions | ||
// server side validations | ||
// check SaveMap api for consistency with the rest of breeze. | ||
module.exports = SequelizeSaveHandler; | ||
function SequelizeSaveHandler(sequelizeManager, req) { | ||
var reqBody = req.body; | ||
this.sequelizeManager = sequelizeManager; | ||
this.metadataStore = sequelizeManager.metadataStore; | ||
this.entitiesFromClient = reqBody.entities; | ||
this.saveOptions = reqBody.saveOptions; | ||
this._keyMappings = []; | ||
this._fkFixupMap = {}; | ||
this._savedEntities = []; | ||
this.keyGenerator = sequelizeManager.keyGenerator; | ||
// this._entitiesCreatedOnServer = []; | ||
}; | ||
var ctor = SequelizeSaveHandler; | ||
ctor.save = function(sequelizeManager, req ) { | ||
var saveHandler = new SequelizeSaveHandler(sequelizeManager, req); | ||
return saveHandler.save(); | ||
}; | ||
// virtual method - returns boolean | ||
//ctor.prototype.beforeSaveEntity = function(entityInfo) | ||
// virtual method - returns nothing | ||
//ctor.prototype.beforeSaveEntities = function(saveMap) | ||
ctor.prototype.save = function() { | ||
var beforeSaveEntity = (this.beforeSaveEntity || noopBeforeSaveEntity).bind(this); | ||
var entityTypeMap = {}; | ||
var entityInfos = this.entitiesFromClient.map(function(entity) { | ||
// transform entities from how they are sent from the client | ||
// into entityInfo objects which is how they are exposed | ||
// to interception on the server. | ||
var entityAspect = entity.entityAspect; | ||
var entityTypeName = entityAspect.entityTypeName; | ||
var entityType = entityTypeMap[entityTypeName]; | ||
if (!entityType) { | ||
entityType = this.metadataStore.getEntityType(entityTypeName); | ||
if (entityType) { | ||
entityTypeMap[entityTypeName] = entityType; | ||
} else { | ||
throw new Error("Unable to locate server side metadata for an EntityType named: " + entityTypeName); | ||
} | ||
/** Handles saving entities from Breeze SaveChanges requests */ | ||
var SequelizeSaveHandler = /** @class */ (function () { | ||
/** Create an instance for the given save request */ | ||
function SequelizeSaveHandler(sequelizeManager, req) { | ||
var reqBody = req.body; | ||
this.sequelizeManager = sequelizeManager; | ||
this.metadataStore = sequelizeManager.metadataStore; | ||
this.entitiesFromClient = reqBody.entities; | ||
this.saveOptions = reqBody.saveOptions; | ||
this._keyMappings = []; | ||
this._fkFixupMap = {}; | ||
this._savedEntities = []; | ||
this.keyGenerator = sequelizeManager.keyGenerator; | ||
} | ||
var unmapped = entity.__unmapped; | ||
var ei = { entity: entity, entityType: entityType, entityAspect: entityAspect, unmapped: unmapped }; | ||
// just to be sure that we don't try to send it to the db server or return it to the client. | ||
delete entity.entityAspect; | ||
return ei; | ||
}, this); | ||
// create the saveMap (entities to be saved) grouped by entityType | ||
var saveMapData = _.groupBy(entityInfos, function(entityInfo) { | ||
// _.groupBy will bundle all undefined returns together. | ||
if (beforeSaveEntity(entityInfo)) { | ||
return entityInfo.entityType.name; | ||
} | ||
}); | ||
// remove the entries where beforeSaveEntity returned false ( they are all grouped under 'undefined' | ||
delete saveMapData["undefined"]; | ||
// want to have SaveMap functions available | ||
var saveMap = _.extend(new SaveMap(this), saveMapData); | ||
return this._saveWithTransaction(saveMap); | ||
}; | ||
ctor.prototype._saveWithTransaction = function(saveMap) { | ||
var that = this; | ||
var sequelize = this.sequelizeManager.sequelize; | ||
return sequelize.transaction().then(function(trx) { | ||
that.transaction = trx; | ||
var nextPromise; | ||
var beforeSaveEntities = (that.beforeSaveEntities || noopBeforeSaveEntities).bind(that); | ||
// beforeSaveEntities will either return nothing or a promise. | ||
nextPromise = Promise.resolve(beforeSaveEntities(saveMap, trx)); | ||
// saveCore returns either a list of entities or an object with an errors property. | ||
return nextPromise.then(function () { | ||
return that._saveCore(saveMap, trx); | ||
}).then(function (r) { | ||
if (r.errors) { | ||
trx.rollback(); | ||
return r; | ||
} else { | ||
trx.commit(); | ||
return { entities: r, keyMappings: that._keyMappings }; | ||
} | ||
}).catch(function (e) { | ||
trx.rollback(); | ||
throw e; | ||
}); | ||
}); | ||
}; | ||
// will be bound to SequelizeSaveHandler instance at runtime. | ||
ctor.prototype._saveCore = function(saveMap, transaction) { | ||
var that = this; | ||
if (saveMap.entityErrors || saveMap.errorMessage) { | ||
return Promise.resolve({ errors: saveMap.entityErrors || [], message: saveMap.errorMessage }); | ||
} | ||
var entityTypes = _.keys(saveMap).map(function (entityTypeName) { | ||
// guaranteed to succeed because these have all been looked up earlier. | ||
return this.metadataStore.getEntityType(entityTypeName); | ||
}, this); | ||
var sortedEntityTypes = toposortEntityTypes(entityTypes); | ||
var entityGroups = sortedEntityTypes.map(function (entityType) { | ||
return { entityType: entityType, entityInfos: saveMap[entityType.name] }; | ||
}); | ||
// do adds/updates first followed by deletes in reverse order. | ||
// add/updates come first because we might move children off of a parent before deleting the parent | ||
// and we don't want to cause a constraint exception by deleting the parent before all of its | ||
// children have been moved somewhere else. | ||
return Promise.reduce(entityGroups, function (savedEntities, entityGroup) { | ||
return that._processEntityGroup(entityGroup, transaction, false).then(function (entities) { | ||
Array.prototype.push.apply(savedEntities, entities); | ||
return savedEntities; | ||
}); | ||
}, []).then(function (entitiesHandledSoFar) { | ||
return Promise.reduce(entityGroups.reverse(), function (savedEntities, entityGroup) { | ||
return that._processEntityGroup(entityGroup, transaction, true).then(function (entities) { | ||
Array.prototype.push.apply(savedEntities, entities); | ||
return savedEntities; | ||
}); | ||
}, entitiesHandledSoFar); | ||
}); | ||
} | ||
// Need to handle | ||
// entityKey._id may be null/undefined for entities created only on the server side - so no need for keyMapping | ||
// returns a promise containing resultEntities array when all entities within the group have been saved. | ||
ctor.prototype._processEntityGroup = function(entityGroup, transaction, processDeleted) { | ||
var entityType = entityGroup.entityType; | ||
var entityInfos = entityGroup.entityInfos.filter(function(entityInfo) { | ||
var isDeleted = entityInfo.entityAspect.entityState == "Deleted" | ||
return processDeleted ? isDeleted : !isDeleted; | ||
}); | ||
var sqModel = this.sequelizeManager.entityTypeSqModelMap[entityType.name]; | ||
// Promise.reduce(["file1.txt", "file2.txt", "file3.txt"], function(total, fileName) { | ||
// return fs.readFileAsync(fileName, "utf8").then(function(contents) { | ||
// return total + parseInt(contents, 10); | ||
// }); | ||
// }, 0).then(function(total) { | ||
// //Total is 30 | ||
// }); | ||
entityInfos = toposortEntityInfos(entityType, entityInfos); | ||
if (processDeleted) | ||
entityInfos = entityInfos.reverse(); | ||
var that = this; | ||
return Promise.reduce(entityInfos, function(savedEntities, entityInfo) { | ||
// function returns a promise for this entity | ||
// and updates the results array. | ||
return that._saveEntityAsync(entityInfo, sqModel, transaction).then(function(savedEntity) { | ||
savedEntities.push(savedEntity); | ||
return savedEntities; | ||
}); | ||
}, []); | ||
}; | ||
// returns a promise with the saved entity | ||
ctor.prototype._saveEntityAsync = function(entityInfo, sqModel, transaction) { | ||
// function returns a promise for this entity | ||
// and updates the results array. | ||
var that = this; | ||
// not a "real" entityAspect - just the salient pieces sent from the client. | ||
var entity = entityInfo.entity; | ||
var entityAspect = entityInfo.entityAspect; | ||
var entityType = entityInfo.entityType; | ||
var entityTypeName = entityType.name; | ||
// TODO: determine if this is needed because we need to strip the entityAspect off the entity for inserts. | ||
entityAspect.entity = entity; | ||
// TODO: we really only need to coerce every field on an insert | ||
// only selected fields are needed for update and delete. | ||
this._coerceData(entity, entityType); | ||
var keyProperties = entityType.keyProperties; | ||
var firstKeyPropName = keyProperties[0].nameOnServer; | ||
var entityState = entityAspect.entityState; | ||
var trxOptions = { transaction: transaction }; | ||
var promise; | ||
if (entityState === "Added") { | ||
var keyMapping = null; | ||
// NOTE: there are two instances of autoGeneratedKeyType available | ||
// one on entityType which is part of the metadata and a second | ||
// on the entityAspect that was sent as part of the save. | ||
// The one on the entityAspect "overrides" the one on the entityType. | ||
var autoGeneratedKey = entityAspect.autoGeneratedKey; | ||
var autoGeneratedKeyType = autoGeneratedKey && autoGeneratedKey.autoGeneratedKeyType; | ||
if (autoGeneratedKeyType && autoGeneratedKeyType !== "None") { | ||
var realKeyValue; | ||
var tempKeyValue = entity[firstKeyPropName]; | ||
if (autoGeneratedKeyType == "KeyGenerator") { | ||
if (this.keyGenerator == null) { | ||
throw new Error("No KeyGenerator was provided for property:" + keyProperties[0].name + " on entityType: " + entityType.name); | ||
/** Save the entities in the save request */ | ||
SequelizeSaveHandler.prototype.save = function () { | ||
var _this = this; | ||
var beforeSaveEntity = (this.beforeSaveEntity || noopBeforeSaveEntity).bind(this); | ||
var entityTypeMap = {}; | ||
var entityInfos = this.entitiesFromClient.map(function (entity) { | ||
// transform entities from how they are sent from the client | ||
// into entityInfo objects which is how they are exposed | ||
// to interception on the server. | ||
var entityAspect = entity.entityAspect; | ||
var entityTypeName = entityAspect.entityTypeName; | ||
var entityType = entityTypeMap[entityTypeName]; | ||
if (!entityType) { | ||
entityType = _this.metadataStore.getEntityType(entityTypeName); | ||
if (entityType) { | ||
entityTypeMap[entityTypeName] = entityType; | ||
} | ||
else { | ||
throw new Error("Unable to locate server side metadata for an EntityType named: " + entityTypeName); | ||
} | ||
} | ||
var unmapped = entity.__unmapped; | ||
var ei = { entity: entity, entityType: entityType, entityAspect: entityAspect, unmapped: unmapped }; | ||
// just to be sure that we don't try to send it to the db server or return it to the client. | ||
delete entity.entityAspect; | ||
return ei; | ||
}, this); | ||
// create the saveMap (entities to be saved) grouped by entityType | ||
var saveMapData = _.groupBy(entityInfos, function (entityInfo) { | ||
// _.groupBy will bundle all undefined returns together. | ||
if (beforeSaveEntity(entityInfo)) { | ||
return entityInfo.entityType.name; | ||
} | ||
}); | ||
// remove the entries where beforeSaveEntity returned false ( they are all grouped under 'undefined' | ||
delete saveMapData["undefined"]; | ||
// want to have SaveMap functions available | ||
var saveMap = _.extend(new SaveMap_1.SaveMap(this), saveMapData); | ||
return this._saveWithTransaction(saveMap); | ||
}; | ||
SequelizeSaveHandler.prototype._saveWithTransaction = function (saveMap) { | ||
var _this = this; | ||
var sequelize = this.sequelizeManager.sequelize; | ||
return sequelize.transaction().then(function (trx) { | ||
// this.transaction = trx; | ||
var beforeSaveEntities = (_this.beforeSaveEntities || noopBeforeSaveEntities).bind(_this); | ||
// beforeSaveEntities will either return nothing or a promise. | ||
var nextPromise = bluebird_1.Promise.resolve(beforeSaveEntities(saveMap, trx)); | ||
// saveCore returns either a list of entities or an object with an errors property. | ||
return nextPromise.then(function (sm) { | ||
return _this._saveCore(saveMap, trx); | ||
}).then(function (r) { | ||
if (r.errors) { | ||
trx.rollback(); | ||
return r; | ||
} | ||
else { | ||
trx.commit(); | ||
return { entities: r, keyMappings: _this._keyMappings }; | ||
} | ||
}).catch(function (e) { | ||
trx.rollback(); | ||
throw e; | ||
}); | ||
}); | ||
}; | ||
; | ||
SequelizeSaveHandler.prototype._saveCore = function (saveMap, transaction) { | ||
var _this = this; | ||
if (saveMap.entityErrors || saveMap.errorMessage) { | ||
return bluebird_1.Promise.resolve({ errors: saveMap.entityErrors || [], message: saveMap.errorMessage }); | ||
} | ||
promise = this.keyGenerator.getNextId(keyProperties[0]).then(function(nextId) { | ||
realKeyValue = nextId; | ||
entity[firstKeyPropName] = realKeyValue; | ||
var entityTypes = _.keys(saveMap).map(function (entityTypeName) { | ||
// guaranteed to succeed because these have all been looked up earlier. | ||
return _this.metadataStore.getEntityType(entityTypeName); | ||
}, this); | ||
var sortedEntityTypes = toposortEntityTypes(entityTypes); | ||
var entityGroups = sortedEntityTypes.map(function (entityType) { | ||
return { entityType: entityType, entityInfos: saveMap[entityType.name] }; | ||
}); | ||
} else if (autoGeneratedKeyType == "Identity") { | ||
var keyDataTypeName = keyProperties[0].dataType.name; | ||
if (keyDataTypeName === "Guid") { | ||
// handled here instead of one the db server. | ||
realKeyValue = createGuid(); | ||
entity[firstKeyPropName] = realKeyValue; | ||
} else { | ||
// realValue will be set during 'create' promise resolution below | ||
realKeyValue = null; | ||
// value will be set by server's autoincrement logic | ||
delete entity[firstKeyPropName]; | ||
// do adds/updates first followed by deletes in reverse order. | ||
// add/updates come first because we might move children off of a parent before deleting the parent | ||
// and we don't want to cause a constraint exception by deleting the parent before all of its | ||
// children have been moved somewhere else. | ||
return bluebird_1.Promise.reduce(entityGroups, function (savedEntities, entityGroup) { | ||
return _this._processEntityGroup(entityGroup, transaction, false).then(function (entities) { | ||
Array.prototype.push.apply(savedEntities, entities); | ||
return savedEntities; | ||
}); | ||
}, []).then(function (entitiesHandledSoFar) { | ||
return bluebird_1.Promise.reduce(entityGroups.reverse(), function (savedEntities, entityGroup) { | ||
return _this._processEntityGroup(entityGroup, transaction, true).then(function (entities) { | ||
Array.prototype.push.apply(savedEntities, entities); | ||
return savedEntities; | ||
}); | ||
}, entitiesHandledSoFar); | ||
}); | ||
}; | ||
SequelizeSaveHandler.prototype._processEntityGroup = function (entityGroup, transaction, processDeleted) { | ||
var _this = this; | ||
var entityType = entityGroup.entityType; | ||
var entityInfos = entityGroup.entityInfos.filter(function (entityInfo) { | ||
var isDeleted = entityInfo.entityAspect.entityState == "Deleted"; | ||
return processDeleted ? isDeleted : !isDeleted; | ||
}); | ||
var sqModel = this.sequelizeManager.entityTypeSqModelMap[entityType.name]; | ||
entityInfos = toposortEntityInfos(entityType, entityInfos); | ||
if (processDeleted) { | ||
entityInfos = entityInfos.reverse(); | ||
} | ||
} | ||
promise = promise || Promise.resolve(null); | ||
promise = promise.then(function() { | ||
// tempKeyValue will be undefined in entity was created on the server | ||
if (tempKeyValue != undefined) { | ||
keyMapping = { entityTypeName: entityTypeName, tempValue: tempKeyValue, realValue: realKeyValue }; | ||
return bluebird_1.Promise.reduce(entityInfos, function (savedEntities, entityInfo) { | ||
// function returns a promise for this entity | ||
// and updates the results array. | ||
return _this._saveEntityAsync(entityInfo, sqModel, transaction).then(function (savedEntity) { | ||
savedEntities.push(savedEntity); | ||
return savedEntities; | ||
}); | ||
}, []); | ||
}; | ||
; | ||
SequelizeSaveHandler.prototype._saveEntityAsync = function (entityInfo, sqModel, transaction) { | ||
// function returns a promise for this entity | ||
// and updates the results array. | ||
var _this = this; | ||
// not a "real" entityAspect - just the salient pieces sent from the client. | ||
var entity = entityInfo.entity; | ||
var entityAspect = entityInfo.entityAspect; | ||
var entityType = entityInfo.entityType; | ||
var entityTypeName = entityType.name; | ||
// TODO: determine if this is needed because we need to strip the entityAspect off the entity for inserts. | ||
entityAspect.entity = entity; | ||
// TODO: we really only need to coerce every field on an insert | ||
// only selected fields are needed for update and delete. | ||
this._coerceData(entity, entityType); | ||
var keyProperties = entityType.keyProperties; | ||
var firstKeyPropName = keyProperties[0].nameOnServer; | ||
var entityState = entityAspect.entityState; | ||
var trxOptions = { transaction: transaction }; | ||
var promise; | ||
if (entityState === "Added") { | ||
var keyMapping_1 = null; | ||
// NOTE: there are two instances of autoGeneratedKeyType available | ||
// one on entityType which is part of the metadata and a second | ||
// on the entityAspect that was sent as part of the save. | ||
// The one on the entityAspect "overrides" the one on the entityType. | ||
var autoGeneratedKey = entityAspect.autoGeneratedKey; | ||
var autoGeneratedKeyType = autoGeneratedKey && autoGeneratedKey.autoGeneratedKeyType; | ||
var tempKeyValue_1 = entity[firstKeyPropName]; | ||
if (autoGeneratedKeyType && autoGeneratedKeyType !== "None") { | ||
var realKeyValue_1; | ||
if (autoGeneratedKeyType == "KeyGenerator") { | ||
if (this.keyGenerator == null) { | ||
throw new Error("No KeyGenerator was provided for property:" + keyProperties[0].name + " on entityType: " + entityType.name); | ||
} | ||
promise = this.keyGenerator.getNextId(keyProperties[0]).then(function (nextId) { | ||
realKeyValue_1 = nextId; | ||
entity[firstKeyPropName] = realKeyValue_1; | ||
}); | ||
} | ||
else if (autoGeneratedKeyType == "Identity") { | ||
var keyDataTypeName = keyProperties[0].dataType.name; | ||
if (keyDataTypeName === "Guid") { | ||
// handled here instead of one the db server. | ||
realKeyValue_1 = createGuid(); | ||
entity[firstKeyPropName] = realKeyValue_1; | ||
} | ||
else { | ||
// realValue will be set during 'create' promise resolution below | ||
realKeyValue_1 = null; | ||
// value will be set by server's autoincrement logic | ||
delete entity[firstKeyPropName]; | ||
} | ||
} | ||
promise = promise || bluebird_1.Promise.resolve(null); | ||
promise = promise.then(function () { | ||
// tempKeyValue will be undefined in entity was created on the server | ||
if (tempKeyValue_1 != undefined) { | ||
keyMapping_1 = { entityTypeName: entityTypeName, tempValue: tempKeyValue_1, realValue: realKeyValue_1 }; | ||
} | ||
}); | ||
} | ||
promise = promise || bluebird_1.Promise.resolve(null); | ||
return promise.then(function () { | ||
return sqModel.create(entity, { transaction: transaction }).then(function (savedEntity) { | ||
if (keyMapping_1) { | ||
if (keyMapping_1.realValue === null) { | ||
keyMapping_1.realValue = savedEntity[firstKeyPropName]; | ||
} | ||
var tempKeyString = buildKeyString(entityType, tempKeyValue_1); | ||
_this._fkFixupMap[tempKeyString] = keyMapping_1.realValue; | ||
_this._keyMappings.push(keyMapping_1); | ||
} | ||
return _this._addToResults(savedEntity.dataValues, entityTypeName); | ||
}).catch(handleItemSaveError(entity, entityState)); | ||
}); | ||
} | ||
}) | ||
} | ||
promise = promise || Promise.resolve(null); | ||
return promise.then(function() { | ||
return sqModel.create(entity, {transaction: transaction}).then(function(savedEntity) { | ||
//return sqModel.create(entity).then(function(savedEntity) { | ||
if (keyMapping) { | ||
if (keyMapping.realValue === null) { | ||
keyMapping.realValue = savedEntity[firstKeyPropName]; | ||
} | ||
var tempKeyString = buildKeyString(entityType, tempKeyValue); | ||
that._fkFixupMap[tempKeyString] = keyMapping.realValue; | ||
that._keyMappings.push(keyMapping); | ||
else if (entityState === "Modified") { | ||
var whereHash_1 = {}; | ||
keyProperties.forEach(function (kp) { | ||
whereHash_1[kp.nameOnServer] = entity[kp.nameOnServer]; | ||
}); | ||
if (entityType.concurrencyProperties && entityType.concurrencyProperties.length > 0) { | ||
entityType.concurrencyProperties.forEach(function (cp) { | ||
// this is consistent with the client behaviour where it does not update the version property | ||
// if its data type is binary | ||
if (cp.dataType.name === 'Binary') | ||
whereHash_1[cp.nameOnServer] = entity[cp.nameOnServer]; | ||
else | ||
whereHash_1[cp.nameOnServer] = entityAspect.originalValuesMap[cp.nameOnServer]; | ||
}); | ||
} | ||
var setHash_1; | ||
if (entityInfo.forceUpdate) { | ||
setHash_1 = _.clone(entity); | ||
// remove fields that we don't want to 'set' | ||
delete setHash_1.entityAspect; | ||
// TODO: should we also remove keyProps here... | ||
} | ||
else { | ||
setHash_1 = {}; | ||
var ovm = entityAspect.originalValuesMap; | ||
if (ovm == null) { | ||
throw new Error("Unable to locate an originalValuesMap for one of the 'Modified' entities to be saved"); | ||
} | ||
Object.keys(ovm).forEach(function (k) { | ||
// if k is one of the entityKeys do no allow this | ||
var isKeyPropName = keyProperties.some(function (kp) { | ||
return kp.nameOnServer == k; | ||
}); | ||
if (isKeyPropName) { | ||
throw new Error("Breeze does not support updating any part of the entity's key insofar as this changes the identity of the entity"); | ||
} | ||
setHash_1[k] = entity[k]; | ||
}); | ||
} | ||
// don't bother executing update statement if nothing to update | ||
// this can happen if setModified is called without any properties being changed. | ||
if (_.isEmpty(setHash_1)) { | ||
return bluebird_1.Promise.resolve(this._addToResults(entity, entityTypeName)); | ||
} | ||
return sqModel.update(setHash_1, { where: whereHash_1, transaction: transaction }).then(function (infoArray) { | ||
var itemsSaved = infoArray[0]; | ||
if (itemsSaved != 1) { | ||
var err = new Error("unable to update entity - concurrency violation"); | ||
err.entity = entity; | ||
err.entityState = entityState; | ||
throw err; | ||
} | ||
// HACK: Sequelize 'update' does not return the entity; so | ||
// we are just returning the original entity here. | ||
return _this._addToResults(entity, entityTypeName); | ||
}).catch(handleItemSaveError(entity, entityState)); | ||
} | ||
return that._addToResults(savedEntity.dataValues, entityTypeName); | ||
}).catch(handleItemSaveError(entity, entityState)); | ||
}); | ||
} else if (entityState === "Modified") { | ||
var whereHash = {}; | ||
keyProperties.forEach(function (kp) { | ||
whereHash[kp.nameOnServer] = entity[kp.nameOnServer]; | ||
}); | ||
if (entityType.concurrencyProperties && entityType.concurrencyProperties.length > 0) { | ||
entityType.concurrencyProperties.forEach(function (cp) { | ||
// this is consistent with the client behaviour where it does not update the version property | ||
// if its data type is binary | ||
if (cp.dataType.name === 'Binary') | ||
whereHash[cp.nameOnServer] = entity[cp.nameOnServer]; | ||
else | ||
whereHash[cp.nameOnServer] = entityAspect.originalValuesMap[cp.nameOnServer]; | ||
}); | ||
} | ||
var setHash; | ||
if (entityInfo.forceUpdate) { | ||
setHash = _.clone(entity); | ||
// remove fields that we don't want to 'set' | ||
delete setHash.entityAspect; | ||
// TODO: should we also remove keyProps here... | ||
} else { | ||
setHash = {}; | ||
var ovm = entityAspect.originalValuesMap; | ||
if (ovm == null) { | ||
throw new Error("Unable to locate an originalValuesMap for one of the 'Modified' entities to be saved"); | ||
} | ||
Object.keys(ovm).forEach(function (k) { | ||
// if k is one of the entityKeys do no allow this | ||
var isKeyPropName = keyProperties.some(function(kp) { | ||
return kp.nameOnServer == k; | ||
else if (entityState = "Deleted") { | ||
var whereHash_2 = {}; | ||
keyProperties.forEach(function (kp) { | ||
whereHash_2[kp.nameOnServer] = entity[kp.nameOnServer]; | ||
}); | ||
// we don't bother with concurrency check on deletes | ||
// TODO: we may want to add a 'switch' for this later. | ||
return sqModel.destroy({ where: whereHash_2, limit: 1, transaction: transaction }).then(function () { | ||
// Sequelize 'destroy' does not return the entity; so | ||
// we are just returning the original entity here. | ||
return _this._addToResults(entity, entityTypeName); | ||
}).catch(handleItemSaveError(entity, entityState)); | ||
} | ||
}; | ||
SequelizeSaveHandler.prototype._addToResults = function (entity, entityTypeName) { | ||
entity.$type = entityTypeName; | ||
this._savedEntities.push(entity); | ||
return entity; | ||
}; | ||
SequelizeSaveHandler.prototype._coerceData = function (entity, entityType) { | ||
var _this = this; | ||
entityType.dataProperties.forEach(function (dp) { | ||
var val = entity[dp.nameOnServer]; | ||
if (val != null) { | ||
if (dp.relatedNavigationProperty != null) { | ||
// if this is an fk column and it has a value | ||
// check if there is a fixed up value. | ||
var key = buildKeyString(dp.relatedNavigationProperty.entityType, val); | ||
var newVal = _this._fkFixupMap[key]; | ||
if (newVal) { | ||
entity[dp.nameOnServer] = newVal; | ||
} | ||
} | ||
var dtName = dp.dataType.name; | ||
if (dtName === "DateTime" || dtName === "DateTimeOffset") { | ||
entity[dp.nameOnServer] = new Date(Date.parse(val)); | ||
} | ||
} | ||
else { | ||
// // this allows us to avoid inserting a null. | ||
// // TODO: think about an option to allow this if someone really wants to. | ||
// delete entity[dp.name]; | ||
// } | ||
} | ||
}); | ||
if (isKeyPropName) { | ||
throw new Error("Breeze does not support updating any part of the entity's key insofar as this changes the identity of the entity"); | ||
} | ||
setHash[k] = entity[k]; | ||
}); | ||
} | ||
var that = this; | ||
// don't bother executing update statement if nothing to update | ||
// this can happen if setModified is called without any properties being changed. | ||
if (_.isEmpty(setHash)) { | ||
return Promise.resolve(that._addToResults(entity, entityTypeName)); | ||
} | ||
return sqModel.update(setHash, { where: whereHash, transaction: transaction }).then(function(infoArray) { | ||
//return sqModel.update(setHash, { where: whereHash }).then(function(infoArray) { | ||
var itemsSaved = infoArray[0]; | ||
if (itemsSaved != 1) { | ||
var err = new Error("unable to update entity - concurrency violation"); | ||
err.entity = entity; | ||
err.entityState = entityState; | ||
throw err; | ||
} | ||
// HACK: Sequelize 'update' does not return the entity; so | ||
// we are just returning the original entity here. | ||
return that._addToResults(entity, entityTypeName); | ||
}).catch(handleItemSaveError(entity, entityState)); | ||
} else if (entityState = "Deleted") { | ||
var whereHash = {}; | ||
keyProperties.forEach(function (kp) { | ||
whereHash[kp.nameOnServer] = entity[kp.nameOnServer]; | ||
}); | ||
// we don't bother with concurrency check on deletes | ||
// TODO: we may want to add a 'switch' for this later. | ||
return sqModel.destroy({ where: whereHash, limit: 1, transaction: transaction}).then(function() { | ||
//return sqModel.destroy({ where: whereHash, limit: 1}).then(function() { | ||
// Sequelize 'destroy' does not return the entity; so | ||
// we are just returning the original entity here. | ||
return that._addToResults(entity, entityTypeName); | ||
}).catch(handleItemSaveError(entity, entityState)) | ||
} | ||
}; | ||
ctor.prototype._addToResults = function(entity, entityTypeName) { | ||
entity.$type = entityTypeName; | ||
this._savedEntities.push(entity); | ||
return entity; | ||
}; | ||
ctor.prototype._coerceData = function(entity, entityType) { | ||
var that = this; | ||
entityType.dataProperties.forEach(function(dp) { | ||
var val = entity[dp.nameOnServer]; | ||
if (val != null) { | ||
if (dp.relatedNavigationProperty != null) { | ||
// if this is an fk column and it has a value | ||
// check if there is a fixed up value. | ||
var key = buildKeyString(dp.relatedNavigationProperty.entityType, val); | ||
var newVal = that._fkFixupMap[key]; | ||
if (newVal) { | ||
entity[dp.nameOnServer] = newVal; | ||
} | ||
} | ||
var dtName = dp.dataType.name; | ||
if (dtName === "DateTime" || dtName === "DateTimeOffset") { | ||
entity[dp.nameOnServer] = new Date(Date.parse(val)); | ||
} | ||
} else { | ||
// // this allows us to avoid inserting a null. | ||
// // TODO: think about an option to allow this if someone really wants to. | ||
// delete entity[dp.name]; | ||
// } | ||
} | ||
}) | ||
}; | ||
return SequelizeSaveHandler; | ||
}()); | ||
exports.SequelizeSaveHandler = SequelizeSaveHandler; | ||
function noopBeforeSaveEntities(saveMap, trx) { | ||
return saveMap; | ||
} | ||
function SaveMap(sequelizeSaveHandler) { | ||
// want to make sequelizeSaveHandler non enumerable. | ||
Object.defineProperty(this, "sequelizeSaveHandler", { value: sequelizeSaveHandler }); | ||
} | ||
SaveMap.prototype.getEntityType = function(entityTypeName) { | ||
return this.sequelizeSaveHandler.metadataStore.getEntityType(entityTypeName); | ||
} | ||
SaveMap.prototype.getEntityInfosOfType = function(entityTypeName) { | ||
var entityType = this.getEntityType(entityTypeName); | ||
// entityType.name is fully qualified. | ||
return this[entityType.name] || []; | ||
} | ||
SaveMap.prototype.addEntity = function(entityTypeName, entity, entityState) { | ||
var entityType = this.getEntityType(entityTypeName); | ||
entityTypeName = entityType.name; // fully qualified now. | ||
var entityInfo = { | ||
entity: entity, entityType: entityType, wasAddedOnServer: true | ||
}; | ||
entityInfo.entityAspect = { | ||
entityTypeName: entityTypeName, | ||
entityState: entityState || "Added" | ||
} | ||
var entityInfoList = this[entityTypeName]; | ||
if (entityInfoList) { | ||
entityInfoList.push(entityInfo); | ||
} else { | ||
this[entityTypeName] = [ entityInfo ]; | ||
} | ||
return entityInfo; | ||
} | ||
SaveMap.prototype.addEntityError = function(entityInfo, errorName, errorMessage, propertyName) { | ||
if (!this.entityErrors) { | ||
this.entityErrors = []; | ||
} | ||
var entityType = entityInfo.entityType; | ||
var keyValues = entityType.keyProperties.map(function (kp) { | ||
return entityInfo.entity[kp.nameOnServer]; | ||
}); | ||
this.entityErrors.push({ | ||
entityTypeName: entityType.name, | ||
errorName: errorName, | ||
errorMessage: errorMessage, | ||
propertyName: propertyName, | ||
keyValues: keyValues | ||
}); | ||
} | ||
SaveMap.prototype.setErrorMessage = function(errorMessage) { | ||
this.errorMessage = errorMessage; | ||
} | ||
// will either return nothing or a promise. | ||
function noopBeforeSaveEntities(saveMap) { | ||
return | ||
} | ||
function noopBeforeSaveEntity(entityInfo) { | ||
return true; | ||
return true; | ||
} | ||
/** Sort the EntityTypes based on their dependencies */ | ||
function toposortEntityTypes(entityTypes) { | ||
var edges = []; | ||
entityTypes.forEach(function(et) { | ||
et.foreignKeyProperties.forEach(function(fkp) { | ||
if (fkp.relatedNavigationProperty) { | ||
var dependsOnType = fkp.relatedNavigationProperty.entityType; | ||
if (et != dependsOnType) { | ||
edges.push( [et, dependsOnType]); | ||
} | ||
} | ||
var edges = []; | ||
entityTypes.forEach(function (et) { | ||
et.foreignKeyProperties.forEach(function (fkp) { | ||
if (fkp.relatedNavigationProperty) { | ||
var dependsOnType = fkp.relatedNavigationProperty.entityType; | ||
if (et != dependsOnType) { | ||
edges.push([et, dependsOnType]); | ||
} | ||
} | ||
}); | ||
}); | ||
}); | ||
// this should work but toposort.array seems to have a bug ... | ||
// var sortedEntityTypes = toposort.array(entityTypes, edges).reverse(); | ||
// so use this instead. | ||
var allSortedTypes = toposort(edges).reverse(); | ||
allSortedTypes.forEach(function(st, ix) { | ||
st.index = ix; | ||
}); | ||
var sortedEntityTypes = entityTypes.sort(function(a, b) { | ||
return a.index - b.index; | ||
}); | ||
return sortedEntityTypes; | ||
// this should work but toposort.array seems to have a bug ... | ||
// let sortedEntityTypes = toposort.array(entityTypes, edges).reverse(); | ||
// so use this instead. | ||
var allSortedTypes = toposort(edges).reverse(); | ||
allSortedTypes.forEach(function (st, ix) { | ||
st.index = ix; | ||
}); | ||
var sortedEntityTypes = entityTypes.sort(function (a, b) { | ||
return a.index - b.index; | ||
}); | ||
return sortedEntityTypes; | ||
} | ||
/** Sort the EntityInfos of a given type based foreign key relationships */ | ||
function toposortEntityInfos(entityType, entityInfos) { | ||
var edges = []; | ||
var selfReferenceNavProp = _.find(entityType.navigationProperties, navProp => navProp.entityType === entityType); | ||
if (!selfReferenceNavProp || !selfReferenceNavProp.relatedDataProperties) | ||
return entityInfos; | ||
var fkDataProp = selfReferenceNavProp.relatedDataProperties[0].name; | ||
var keyProp = entityType.keyProperties[0].name; | ||
entityInfos.forEach(function(entityInfo) { | ||
var dependsOn = entityInfo.entity[fkDataProp]; | ||
if (dependsOn) { | ||
var dependsOnInfo = _.find(entityInfos, x => x.entity[keyProp] === dependsOn && x.entity !== entityInfo.entity); // avoid referencing the same object | ||
if (dependsOnInfo) | ||
edges.push([entityInfo, dependsOnInfo]); | ||
var edges = []; | ||
var selfReferenceNavProp = _.find(entityType.navigationProperties, function (navProp) { return navProp.entityType === entityType; }); | ||
if (!selfReferenceNavProp || !selfReferenceNavProp.relatedDataProperties) { | ||
return entityInfos; | ||
} | ||
}); | ||
if (edges.length === 0) | ||
return entityInfos; | ||
var allSortedEntityInfos = toposort(edges).reverse(); | ||
allSortedEntityInfos.forEach(function(st, ix) { | ||
st.__index = ix; | ||
}); | ||
var sortedEntityInfos = entityInfos.sort(function(a, b) { | ||
return a.__index - b.__index; | ||
}); | ||
return sortedEntityInfos; | ||
var fkDataProp = selfReferenceNavProp.relatedDataProperties[0].name; | ||
var keyProp = entityType.keyProperties[0].name; | ||
entityInfos.forEach(function (entityInfo) { | ||
var dependsOn = entityInfo.entity[fkDataProp]; | ||
if (dependsOn) { | ||
var dependsOnInfo = _.find(entityInfos, function (x) { return x.entity[keyProp] === dependsOn && x.entity !== entityInfo.entity; }); // avoid referencing the same object | ||
if (dependsOnInfo) | ||
edges.push([entityInfo, dependsOnInfo]); | ||
} | ||
}); | ||
if (edges.length === 0) { | ||
return entityInfos; | ||
} | ||
var allSortedEntityInfos = toposort(edges).reverse(); | ||
allSortedEntityInfos.forEach(function (st, ix) { | ||
st.__index = ix; | ||
}); | ||
var sortedEntityInfos = entityInfos.sort(function (a, b) { | ||
return a.__index - b.__index; | ||
}); | ||
return sortedEntityInfos; | ||
} | ||
function buildKeyString(entityType, val) { | ||
return entityType.name + "::" + val.toString(); | ||
return entityType.name + "::" + val.toString(); | ||
} | ||
function handleItemSaveError(entity, entityState) { | ||
return function(err) { | ||
err = typeof(err) == 'string' ? new Error(err) : err; | ||
var detailedMsg = (err.name ? "error name: " + err.name : "") + ( err.sql ? " sql: " + err.sql : ""); | ||
err.message = err.message ? err.message + ". " + detailedMsg : detailedMsg; | ||
err.entity = entity; | ||
err.entityState = entityState; | ||
throw err; | ||
} | ||
return function (err) { | ||
err = typeof (err) == 'string' ? new Error(err) : err; | ||
var detailedMsg = (err.name ? "error name: " + err.name : "") + (err.sql ? " sql: " + err.sql : ""); | ||
err.message = err.message ? err.message + ". " + detailedMsg : detailedMsg; | ||
err.entity = entity; | ||
err.entityState = entityState; | ||
throw err; | ||
}; | ||
} | ||
function createGuid() { | ||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | ||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | ||
return v.toString(16); | ||
}); | ||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | ||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | ||
return v.toString(16); | ||
}); | ||
} | ||
// Not needed. | ||
//// task is a function that returns a promise. | ||
//function sequencePromises(tasks) { | ||
// var current = Promise.resolve(), results = []; | ||
// for (var k = 0; k < tasks.length; ++k) { | ||
// results.push(current = current.then(tasks[k])); | ||
// } | ||
// return Promise.all(results); | ||
//} | ||
//# sourceMappingURL=SequelizeSaveHandler.js.map |
18
utils.js
@@ -1,8 +0,14 @@ | ||
exports.log = log; | ||
exports.log.enabled = true; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function log(s) { | ||
if (!log.enabled) return; | ||
console.log('[Breeze] ' + s + '\n'); | ||
var args = []; | ||
for (var _i = 1; _i < arguments.length; _i++) { | ||
args[_i - 1] = arguments[_i]; | ||
} | ||
if (!log['enabled']) | ||
return; | ||
console.log('[Breeze] ' + s + '\n', args); | ||
} | ||
exports.log = log; | ||
log['enabled'] = true; | ||
//# sourceMappingURL=utils.js.map |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
22
1886
25
86958
8
1
+ Addedbreeze-client@2.1.5(transitive)
+ Addedtslib@1.14.1(transitive)
- Removedbreeze-client@1.7.2(transitive)
Updatedbluebird@^3.7.2
Updatedbreeze-client@>=2.0.3
Updatedsequelize@^5.21.3