-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Convert to TypeScript, for maintainability and type definitions
- Loading branch information
1 parent
e339a2b
commit f2bda31
Showing
34 changed files
with
2,102 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,8 @@ | |
*.csproj | ||
*.sln | ||
*.log | ||
*.tgz | ||
.vscode | ||
|
||
node_modules | ||
bin | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# breeze-sequelize | ||
|
||
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. | ||
|
||
## Repo | ||
|
||
The [src](./src) folder contains the TypeScript source code. | ||
|
||
The [test](./test) folder contains the unit tests (work in progress). | ||
|
||
The [test/ExpressDemo](./test/ExpressDemo) folder contains a complete server using breeze-sequelize, for running an end-to-end test suite. | ||
|
||
## Documentation | ||
|
||
[Breeze/Sequelize documentation here](http://breeze.github.io/doc-node-sequelize/ "breeze-sequelize documentation") | ||
|
||
[Learn more about Breeze](http://breeze.github.io/doc-js/ "breezejs"). | ||
|
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# The "breeze-sequelize" npm package | ||
|
||
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. | ||
|
||
To install with npm, open a terminal or command window and enter: | ||
|
||
`npm install breeze-sequelize` | ||
|
||
>Case matters! Be sure to spell "breeze-sequelize" in all lowercase. | ||
[Learn more about Breeze](http://breeze.github.io/doc-js/ "breezejs"). | ||
|
||
## More documentation | ||
|
||
[Breeze/Sequelize documentation here](http://breeze.github.io/doc-node-sequelize/ "breeze-sequelize documentation") | ||
|
||
|
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "breeze-sequelize", | ||
"version": "0.3.0", | ||
"description": "Breeze Sequelize server implementation", | ||
"keywords": [ | ||
"breeze", | ||
"sequelize", | ||
"orm", | ||
"query", | ||
"linq", | ||
"graph" | ||
], | ||
"main": "main.js", | ||
"directories": {}, | ||
"dependencies": { | ||
"bluebird": "^3.5.5", | ||
"breeze-client": "^2.0.2", | ||
"lodash": "^4.17.15", | ||
"sequelize": "^5.21.3", | ||
"toposort": "^2.0.2" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.2.0", | ||
"mocha": "^6.2.0" | ||
}, | ||
"scripts": { | ||
"test": "mocha" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/Breeze/breeze.server.node.git" | ||
}, | ||
"homepage": "http://breeze.github.io/doc-node-sequelize/introduction.html", | ||
"bugs": "https://github.com/Breeze/breeze.server.node/issues", | ||
"author": { | ||
"name": "IdeaBlade", | ||
"email": "[email protected]", | ||
"url": "https://www.ideablade.com/" | ||
}, | ||
"contributors": [ | ||
"Jay Traband", | ||
"Steve Schmitt", | ||
"Marcel Good", | ||
"Ward Bell" | ||
], | ||
"license": "MIT" | ||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { breeze, ComplexType, DataProperty, EntityType, MetadataStore, NavigationProperty, StructuralType } from "breeze-client"; | ||
import { AbstractDataType, DataTypes, Model, ModelAttributeColumnOptions, ModelAttributes, ModelOptions, Sequelize } from "sequelize"; | ||
import * as _ from 'lodash'; | ||
import * as utils from "./utils"; | ||
|
||
let log = utils.log; | ||
|
||
export interface NameModelMap { [modelName: string]: { new(): Model } & typeof Model }; | ||
|
||
// TODO: still need to handle inherited entity types - TPT | ||
/** Maps Breeze metadata to Sequelize Models */ | ||
export class MetadataMapper { | ||
sequelize: Sequelize | ||
metadataStore: MetadataStore; | ||
entityTypeSqModelMap: NameModelMap; | ||
resourceNameSqModelMap: NameModelMap; | ||
|
||
constructor(breezeMetadata: MetadataStore | string | Object, sequelize: Sequelize) { | ||
this.sequelize = sequelize; | ||
let ms; | ||
if (breezeMetadata instanceof MetadataStore) { | ||
ms = breezeMetadata; | ||
} else { | ||
ms = new breeze.MetadataStore(); | ||
ms.importMetadata(breezeMetadata); | ||
} | ||
|
||
this.metadataStore = ms; | ||
this._createMaps(); | ||
} | ||
|
||
/** creates entityTypeSqModelMap and resourceNameSqModelMap */ | ||
private _createMaps() { | ||
|
||
let ms = this.metadataStore; | ||
let allTypes = ms.getEntityTypes(); | ||
let typeMap = _.groupBy(allTypes, t => { | ||
return t.isComplexType ? "complexType" : "entityType"; | ||
}); | ||
// let complexTypes = typeMap["complexType"]; | ||
let entityTypes = typeMap["entityType"]; | ||
|
||
// map of entityTypeName to sqModel | ||
let entityTypeSqModelMap: NameModelMap = this.entityTypeSqModelMap = {}; | ||
// first create all of the sequelize types with just data properties | ||
entityTypes.forEach(entityType => { | ||
let typeConfig = this.mapToSqModelConfig(entityType); | ||
let options: ModelOptions = { | ||
// 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 | ||
}; | ||
let sqModel = this.sequelize.define(entityType.shortName, typeConfig, options) as { new(): Model } & typeof Model; | ||
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, (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 | ||
|
||
/** Adds relationships to the Models based on Breeze NavigationProperties */ | ||
private createNavProps(entityTypes: StructuralType[], entityTypeSqModelMap: NameModelMap) { | ||
// TODO: we only support single column foreignKeys for now. | ||
|
||
entityTypes.forEach(entityType => { | ||
let navProps = entityType.navigationProperties as NavigationProperty[]; | ||
let sqModel = entityTypeSqModelMap[entityType.name]; | ||
navProps.forEach(np => { | ||
let npName = np.nameOnServer; | ||
|
||
let targetEntityType = np.entityType; | ||
let 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 */ | ||
private mapToSqModelConfig(entityOrComplexType: StructuralType): ModelAttributes { | ||
// propConfig looks like | ||
// { firstProp: { type: Sequelize.XXX, ... }, | ||
// secondProp: { type: Sequelize.XXX, ... } | ||
// .. | ||
// } | ||
|
||
let typeConfig = {} as ModelAttributes; | ||
entityOrComplexType.dataProperties.forEach(dataProperty => { | ||
let propConfig = this.mapToSqPropConfig(dataProperty); | ||
_.merge(typeConfig, propConfig); | ||
}); | ||
|
||
return typeConfig; | ||
} | ||
|
||
/** Creates Sequelize column attributes based on a DataProperty */ | ||
private mapToSqPropConfig(dataProperty: DataProperty): ModelAttributes { | ||
if (dataProperty.isComplexProperty) { | ||
return this.mapToSqModelConfig(dataProperty.dataType as ComplexType); | ||
} | ||
let propConfig = {} as ModelAttributes; | ||
let attributes = {} as ModelAttributeColumnOptions; | ||
propConfig[dataProperty.nameOnServer] = attributes; | ||
let sqModel = _dataTypeMap[dataProperty.dataType.name]; | ||
if (sqModel == null) { | ||
let 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 = DataTypes.STRING(dataProperty.maxLength); | ||
} | ||
if (!dataProperty.isNullable) { | ||
attributes.allowNull = false; | ||
} | ||
if (dataProperty.isPartOfKey) { | ||
attributes.primaryKey = true; | ||
if ((dataProperty.parentType as EntityType).autoGeneratedKeyType == breeze.AutoGeneratedKeyType.Identity) { | ||
let dt = attributes.type as AbstractDataType; | ||
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; | ||
} | ||
} | ||
|
||
|
||
let _dataTypeMap = { | ||
String: DataTypes.STRING, | ||
Boolean: DataTypes.BOOLEAN, | ||
DateTime: DataTypes.DATE, | ||
DateTimeOffset: DataTypes.DATE, | ||
Byte: DataTypes.INTEGER.UNSIGNED, | ||
Int16: DataTypes.INTEGER, | ||
Int32: DataTypes.INTEGER, | ||
Int64: DataTypes.BIGINT, | ||
Decimal: DataTypes.DECIMAL(19, 4), | ||
Double: DataTypes.FLOAT, | ||
Single: DataTypes.FLOAT, | ||
Guid: DataTypes.UUID, | ||
Binary: DataTypes.STRING().BINARY, | ||
Time: DataTypes.STRING, | ||
Undefined: DataTypes.BLOB | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.