From 57f329799973efea1f2bb4e93c9ae158cdd042e0 Mon Sep 17 00:00:00 2001 From: scott-wyatt Date: Tue, 10 Jul 2018 20:20:49 -0400 Subject: [PATCH 1/2] [feat] Fabrix Resolveres --- README.md | 30 +-- lib/SequelizeResolver.ts | 288 ++++++++++++++++++++++++++++ lib/SequelizeSpool.ts | 10 +- lib/api/services/TapestryService.ts | 12 +- lib/index.ts | 1 + lib/transformer.ts | 170 +++++++++++++--- package-lock.json | 25 +-- package.json | 10 +- test/app.js | 39 +++- test/integrations/spool.test.js | 22 +++ test/testmodel.js | 11 +- 11 files changed, 545 insertions(+), 73 deletions(-) create mode 100644 lib/SequelizeResolver.ts diff --git a/README.md b/README.md index 7d525b6..1818c15 100755 --- a/README.md +++ b/README.md @@ -37,8 +37,11 @@ A basic `config/store.js` can be found here : https://github.com/fabrix-app/spoo ### Models ```js -module.exports = class User extends Model { - //More about supported schema here : http://docs.sequelizejs.com/en/latest/docs/models-definition/ +import { FabrixModel as Model } from '@fabrix/fabrix/dist/common' +import { SequelizeResolver } from '@fabrix/spool-sequelize' + +export class User extends Model { + // More about supported schema here : http://docs.sequelizejs.com/en/latest/docs/models-definition/ static schema (app, Sequelize) { return { name: { type: Sequelize.STRING, allowNull: false }, @@ -51,21 +54,26 @@ module.exports = class User extends Model { return { migrate: 'drop', //override default models configurations if needed store: 'sqlite', //override default models configurations if needed - //More informations about supported models options here : http://docs.sequelizejs.com/en/latest/docs/models-definition/#configuration + // More informations about supported models options here : http://docs.sequelizejs.com/en/latest/docs/models-definition/#configuration options: {} } } + // The Way this model interacts with Sequelize + static get resolver () { + return SequelizeResolver + } + // If you need associations, put them here associate(models) { - //More information about associations here : http://docs.sequelizejs.com/en/latest/docs/associations/ - models.User.hasMany(models.Role, { - as: 'roles', - onDelete: 'CASCADE', - foreignKey: { - allowNull: true - } - }) + // More information about associations here : http://docs.sequelizejs.com/en/latest/docs/associations/ + models.User.hasMany(models.Role, { + as: 'roles', + onDelete: 'CASCADE', + foreignKey: { + allowNull: true + } + }) } } ``` diff --git a/lib/SequelizeResolver.ts b/lib/SequelizeResolver.ts new file mode 100644 index 0000000..cbe9db8 --- /dev/null +++ b/lib/SequelizeResolver.ts @@ -0,0 +1,288 @@ +import { FabrixModel, FabrixResolver } from '@fabrix/fabrix/dist/common' +import { Sequelize, Model, DataTypes } from 'sequelize' +import { Transformer } from './transformer' + +export class SequelizeResolver extends FabrixResolver { + private _connection + private _sequelizeModel + + constructor (model: FabrixModel, datastore?: Sequelize) { + super(model) + if (!model) { + throw new RangeError('Resolver must be given a Model to bind to') + } + } + + get connection() { + return this._connection + } + + set connection(connection) { + this._connection = connection + } + + get sequelizeModel() { + return this._sequelizeModel + } + + public connect(modelName, schema, options) { + this._sequelizeModel = this._connection.define(modelName, schema, options) + + const instanceMethods = Transformer.getModelPrototypes(this.model) + const classMethods = Transformer.getModelMethods(this.model, instanceMethods) + + // Assign Class Methods from the Model + Object.keys(classMethods).forEach(c => { + this._sequelizeModel[c] = classMethods[c] + }) + + // Assign Instance Methods from the Model + Object.keys(instanceMethods).forEach(i => { + this._sequelizeModel.prototype[i] = instanceMethods[i] + }) + + // Add this model to the connection.models for use later + this._connection.models[modelName] = this._sequelizeModel + + // Bind the new methods to the models + const resolverMethods = Transformer.getClassMethods(this) + Object.entries(resolverMethods).forEach(([ _, method]: [any, string]) => { + this.model[method] = this[method].bind(this) + }) + } + + /** + * Getters + */ + get associations() { + if (this._sequelizeModel) { + return this._sequelizeModel.associations + } + } + + /** + * Class Methods + */ + + addScope(name, scope, options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.addScope(name, scope, options) + } + } + + aggregate(filed, aggregateFunction, options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.aggregate(filed, aggregateFunction, options) + } + } + + belongsTo(target, options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.belongsTo(target, options) + } + } + + belongsToMany(target, options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.belongsToMany(target, options) + } + } + + build(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.build(options) + } + } + + bulkCreate(records: any[], options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.bulkCreate(records, options) + } + } + + count(criteria, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.count(criteria, options) + } + } + + create (values, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.create(values, options) + } + } + + decrement(fields, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.decrement(fields, options) + } + } + describe(schema, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.describe(schema, options) + } + } + + // Delete is a Fabrix Alias of Sequelize.destroy + delete(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.destroy(options) + } + } + + destroy(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.destroy(options) + } + } + + drop(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.drop(options) + } + } + + findAll(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.findAll(options) + } + } + + findAndCountAll(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.findAndCountAll(options) + } + } + + findById(id, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.findById(id, options) + } + } + + findOne(criteria, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.findOne(criteria, options) + } + } + + findOrBuild(criteria, defaults, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.findOrBuild(criteria, defaults, options) + } + } + + findOrCreate(criteria, defaults, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.findOrCreate(criteria, defaults, options) + } + } + + getTableName() { + if (this._sequelizeModel) { + return this._sequelizeModel.getTableName() + } + } + + hasMany(arget, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.hasMany(arget, options) + } + } + + hasOne(target, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.hasOne(target, options) + } + } + + increment(fields, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.increment(fields, options) + } + } + + init(attributes, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.init(attributes, options) + } + } + + max(criteria, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.max(criteria, options) + } + } + + min(criteria, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.min(criteria, options) + } + } + + removeAttribute(attribute) { + if (this._sequelizeModel) { + return this._sequelizeModel.removeAttribute(attribute) + } + } + + restore(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.restore(options) + } + } + + // Conflicts with Fabrix Resolver? + // schema(schema, options = { }) { + // + // } + + scope(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.scope(options) + } + } + + sum(riteria, options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.sum(riteria, options) + } + } + + sync(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.sync(options) + } + } + + truncate(options = { }) { + if (this._sequelizeModel) { + return this._sequelizeModel.truncate(options) + } + } + + unscoped() { + if (this._sequelizeModel) { + return this._sequelizeModel.unscoped() + } + } + + // Save is a Fabrix Alias of Sequelize.update + save(values, options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.update(values, options) + } + } + + update(values, options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.update(values, options) + } + } + + upsert(values, options = {}) { + if (this._sequelizeModel) { + return this._sequelizeModel.upsert(values, options) + } + } +} diff --git a/lib/SequelizeSpool.ts b/lib/SequelizeSpool.ts index 2bf10bd..c998398 100755 --- a/lib/SequelizeSpool.ts +++ b/lib/SequelizeSpool.ts @@ -14,6 +14,7 @@ import * as api from './api/index' export class SequelizeSpool extends DatastoreSpool { _datastore = Sequelize + private _connections: {[key: string]: any} = { } private _models: {[key: string]: any} = { } @@ -65,15 +66,10 @@ export class SequelizeSpool extends DatastoreSpool { * database. */ async initialize() { + // Holds a collection of the connections made through Sequelize this._connections = Transformer.getConnections(this.app) + // Holds a collection of the Sequelize models this._models = Transformer.getModels(this.app, this.connections) - - // Replaces the app sequelize models with their sequelize versions - // The originals are still in app.api.models - Object.keys(this.models).forEach( m => { - this.app.models[m] = this.models[m] - }) - // Migrate the connections and/or models by their migration strategy return this.migrate() } diff --git a/lib/api/services/TapestryService.ts b/lib/api/services/TapestryService.ts index ef61ca9..2978cbe 100755 --- a/lib/api/services/TapestryService.ts +++ b/lib/api/services/TapestryService.ts @@ -23,10 +23,20 @@ export class TapestryService extends Service { * @private */ _getModel(modelName) { - return this.app.models[modelName] + const model = this.app.models[modelName] || this.app.spools['sequelize'].models[modelName] || _.find(this.app.models, {tableName: modelName}) || _.find(this.app.spools['sequelize'].models, {tableName: modelName}) + + if (model && model.resolver && model.resolver.sequelizeModel) { + return model.resolver.sequelizeModel + } + else if (model && model.sequelizeModel) { + return model.sequelizeModel + } + else { + return model + } } /** diff --git a/lib/index.ts b/lib/index.ts index d5f850f..e68dd74 100755 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,6 +1,7 @@ import * as Errors from './errors' export { SequelizeSpool } from './SequelizeSpool' +export { SequelizeResolver } from './SequelizeResolver' export { Transformer } from './transformer' export { Validator } from './validator' export { Schemas } from './schemas' diff --git a/lib/transformer.ts b/lib/transformer.ts index d247831..1abd240 100755 --- a/lib/transformer.ts +++ b/lib/transformer.ts @@ -5,6 +5,89 @@ import { FabrixModel } from '@fabrix/fabrix/dist/common' import { pickBy, isString, startsWith } from 'lodash' export const Transformer = { + BreakException: {}, + + reservedMethods: [ + '_app', + '_datastore', + 'app', + 'api', + 'log', + '__', // this reserved method comes from i18n + 'constructor', + 'undefined', + 'methods', + 'config', + 'schema', + 'services', + 'models', + 'connect' + ], + // Supplied by Model vs Recognized by Sequelize + // Generally Fabrix ORMS support: string, int, date + dataTypes: { + '^(STRING|string)': 'STRING', + '^(STRING|string)\((\w*)\)': 'STRING($2)', + '(STRING.BINARY)': 'STRING.BINARY', + '^(TEXT|text)': 'TEXT', + '^(TEXT|text)\((\w*)\)': 'TEXT($2)', + '^(INTEGER|integer|int)': 'INTEGER', + '^(BIGINT)': 'BIGINT', + '^(BIGINT)\((\d*)\)': 'BIGINT($2)', + '^(FLOAT)': 'FLOAT', + '^(FLOAT)\((\d*)\)': 'FLOAT($2)', + '^(FLOAT)\((\d*),\s(\d*)\)': 'FLOAT($2, $3)', + '^(REAL)': 'REAL', + '^(REAL)\((\d*)\)': 'REAL($2)', + '^(REAL)\((\d*),\s(\d*)\)': 'REAL($2, $3)', + '^(DOUBLE)': 'DOUBLE', + '^(DOUBLE)\((\d*)\)': 'DOUBLE($2)', + '^(DOUBLE)\((\d*),\s(\d*)\)': 'DOUBLE($2, $3)', + '^(DECIMAL)': 'DECIMAL', + '^(DECIMAL)\((\d*),\s(\d*)\)': 'DECIMAL($2, $3)', + '^(DATE|date)': 'DATE', + '^(DATE)\((\d*)\)': 'DATE($2)', + '^(DATEONLY)': 'DATEONLY', + '^(BOOLEAN)': 'BOOLEAN', + '^(ENUM)': 'ENUM', + '^(ENUM)\((.*)?\)': 'ENUM($2)', + '^(ARRAY)\((\w*)\)': 'ARRAY($2)', + '^(JSON|json)': 'JSON', + '^(JSONB|jsonb)': 'JSONB', + '^(BLOB)': 'BLOB', + '^(BLOB)\((\w*)\)': 'BLOB($2)', + '^(UUID)': 'UUID', + '^(CIDR)': 'CIDR', + '^(INET)': 'INET', + '^(MACADDR)': 'MACADDR', + '^(RANGE)\((\w*)\)': 'RANGE($2)', + '^(GEOMETRY)': 'GEOMETRY', + '^(GEOMETRY)\((\w*)\)': 'GEOMETRY($2)', + '^(GEOMETRY)\((\w*),\s(\d*)\)': 'GEOMETRY($2, $3)' + }, + + /** + * Traverse prototype chain and aggregate all class method names + */ + getClassMethods (obj: any): string[] { + const props: string[] = [ ] + const objectRoot = new Object() + + while (!obj.isPrototypeOf(objectRoot)) { + Object.getOwnPropertyNames(obj).forEach(prop => { + if ( + props.indexOf(prop) === -1 + && !Transformer.reservedMethods.some(p => p === prop) + && typeof obj[prop] === 'function' + ) { + props.push(prop) + } + }) + obj = Object.getPrototypeOf(obj) + } + return props + }, + getModelOptions: (app: FabrixApp, model) => { const config = model.constructor.config(app, Sequelize) // Options must be @@ -20,13 +103,50 @@ export const Transformer = { }, getModelSchema: (app: FabrixApp, model) => { - return model.constructor.schema(app, Sequelize) + const schema = Transformer.transformSchema(model.constructor.schema(app, Sequelize)) + return schema }, + /** + * Transforms Schema to Sequelize method if defined as a string + * Common from Spools built for waterline + */ + transformSchema: (schema) => { + const transformed: {[key: string]: any } = {} + Object.keys(schema).forEach(s => { + if (typeof schema[s] === 'string') { + try { + Object.keys(Transformer.dataTypes).forEach(type => { + const exp = new RegExp(type) + if (exp.test(schema[s])) { + transformed[s] = Sequelize[schema[s].replace(exp, Transformer.dataTypes[type])] + throw Transformer.BreakException + } + }) + } + catch (e) { + if (e !== Transformer.BreakException) { + throw e + } + } + } + else { + transformed[s] = schema[s] + } + }) + return transformed + }, + + /** + * Get the prototypes of a model + */ getModelPrototypes: (model) => { return Object.getPrototypeOf(model) }, + /** + * Get the Methods of a model + */ getModelMethods: (model, prototypes) => { const methods = {} const methodNames = model.methods.filter(m => Object.keys(prototypes).indexOf(m) === -1) @@ -34,38 +154,33 @@ export const Transformer = { return methods }, - defineModel: (app: FabrixApp, model, connections) => { + defineModel: (app: FabrixApp, model: FabrixModel, connections) => { const modelName = model.constructor.name const modelConfig = model.config const store = modelConfig.store || app.config.get('models.defaultStore') const connection = connections[store] const migrate = modelConfig.migrate || app.config.get('models.migrate') || connection.migrate - const instanceMethods = Transformer.getModelPrototypes(model) - const classMethods = Transformer.getModelMethods(model, instanceMethods) + // const instanceMethods = Transformer.getModelPrototypes(model) + // const classMethods = Transformer.getModelMethods(model, instanceMethods) const options = Transformer.getModelOptions(app, model) const schema = Transformer.getModelSchema(app, model) - const SequelizeModel = connection.define(modelName, schema, options) - SequelizeModel.store = store - SequelizeModel.migrate = migrate + // const SequelizeModel = connection.define(modelName, schema, options) + // model.resolver.store = store + // model.resolver.migrate = migrate + // console.log('BROKE BEFORE', typeof model.resolver.create) + model.store = store + model.migrate = migrate + model.resolver.connection = connection + model.resolver.connect(modelName, schema, options) - Object.keys(classMethods).forEach(c => { - SequelizeModel[c] = classMethods[c] - }) - - Object.keys(instanceMethods).forEach(i => { - SequelizeModel.prototype[i] = instanceMethods[i] - }) - - connection.models[modelName] = SequelizeModel - - return SequelizeModel + return model }, /** * Create Sequelize object based on config options - * @param {Object} config fabrix.js store - * @return {Sequelize} Sequelize instance + * @param {Object} app.config.store + * @return {Sequelize} Sequelize instance */ createConnectionsFromConfig (config: {[key: string]: any}) { if (config.uri) { @@ -82,7 +197,7 @@ export const Transformer = { }, /** - * Pick only SQL stores from app config + * Pick only Sequelize SQL stores from app config */ pickStores (stores): {[key: string]: any} { return pickBy(stores, (_store, name) => { @@ -127,23 +242,24 @@ export const Transformer = { */ getModels (app: FabrixApp, connections) { const models = Transformer.pickModels(app, connections) - const sequelize = {} + const sModels = {} Object.keys(models).forEach(modelName => { - sequelize[modelName] = Transformer.defineModel(app, models[modelName], connections) + sModels[modelName] = Transformer.defineModel(app, models[modelName], connections).resolver.sequelizeModel }) - Transformer.associateModels(app, sequelize) - return sequelize + Transformer.associateModels(app, models, sModels) + return sModels }, /** * Call the associate method on configured models */ - associateModels (app: FabrixApp, models) { + associateModels (app: FabrixApp, models, sequelizeModels) { Object.keys(models).forEach( modelName => { // Associate the models if (models[modelName].hasOwnProperty('associate')) { - models[modelName].associate(models) + models[modelName].associate(sequelizeModels) } + models[modelName].associations = models[modelName].resolver.sequelizeModel.associations }) } } diff --git a/package-lock.json b/package-lock.json index 9839aaa..ae9bf8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@fabrix/spool-sequelize", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -114,9 +114,9 @@ } }, "@fabrix/fabrix": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@fabrix/fabrix/-/fabrix-1.0.1.tgz", - "integrity": "sha512-MIwsYaj54P6+3subkdkLxVWxz1v1+0Px4m2q2zhtm0T3yiam0JGEEb5MnNjSCTSPxraXU2zCMHEjhkKBd32Ewg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@fabrix/fabrix/-/fabrix-1.0.6.tgz", + "integrity": "sha512-D0gv14aN5SV6ESF/y3BNj+PDlc34mtD2kLDOdzj3RqpiwxiISxipzPg0z5LZgGJBTuN8JygHlqmWnBRwwOmCDA==", "dev": true, "requires": { "lodash": "4.17.10", @@ -150,16 +150,14 @@ } }, "@types/bluebird": { - "version": "3.5.20", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.20.tgz", - "integrity": "sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA==", - "dev": true + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.21.tgz", + "integrity": "sha512-6UNEwyw+6SGMC/WMI0ld0PS4st7Qq51qgguFrFizOSpGvZiqe9iswztFSdZvwJBEhLOy2JaxNE6VC7yMAlbfyQ==" }, "@types/continuation-local-storage": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", "integrity": "sha1-oz4N+dzptCTRyY/E/evYV43O7H4=", - "dev": true, "requires": { "@types/node": "10.3.4" } @@ -172,8 +170,7 @@ "@types/lodash": { "version": "4.14.110", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.110.tgz", - "integrity": "sha512-iXYLa6olt4tnsCA+ZXeP6eEW3tk1SulWeYyP/yooWfAtXjozqXgtX4+XUtMuOCfYjKGz3F34++qUc3Q+TJuIIw==", - "dev": true + "integrity": "sha512-iXYLa6olt4tnsCA+ZXeP6eEW3tk1SulWeYyP/yooWfAtXjozqXgtX4+XUtMuOCfYjKGz3F34++qUc3Q+TJuIIw==" }, "@types/node": { "version": "10.3.4", @@ -184,9 +181,8 @@ "version": "4.27.23", "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.27.23.tgz", "integrity": "sha512-swWlbr1E9cqnSfiKerhmyMlAYSAWiPJyROKHd9nadNuaF1qvZPFfCJ7bGBlhLpEQqeDPVyUSo2Guv+CP1NUY5w==", - "dev": true, "requires": { - "@types/bluebird": "3.5.20", + "@types/bluebird": "3.5.21", "@types/continuation-local-storage": "3.2.1", "@types/lodash": "4.14.110", "@types/validator": "9.4.1" @@ -195,8 +191,7 @@ "@types/validator": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-9.4.1.tgz", - "integrity": "sha512-Y8UyLZvBPgckGhEIFCGBPj1tsRbpcZn4rbAp7lUxC3EW/nDR2V6t9LltE+mvDJxQQ+Bg3saE3UAwn6lsG5O1yQ==", - "dev": true + "integrity": "sha512-Y8UyLZvBPgckGhEIFCGBPj1tsRbpcZn4rbAp7lUxC3EW/nDR2V6t9LltE+mvDJxQQ+Bg3saE3UAwn6lsG5O1yQ==" }, "ansi-regex": { "version": "2.1.1", diff --git a/package.json b/package.json index 89d8bab..25db918 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fabrix/spool-sequelize", - "version": "1.0.0", + "version": "1.0.1", "description": "Spool - Datastore Spool for Sequelize.js http://sequelizejs.com", "scripts": { "build": "tsc -p ./lib/tsconfig.release.json", @@ -46,18 +46,18 @@ "url": "git+https://github.com/fabrix-app/spool-sequelize.git" }, "dependencies": { - "lodash": "^4.17.10", + "@types/sequelize": "^4.27.23", "joi": "^13.4.0", + "lodash": "^4.17.10", "sequelize": "^4.37.10" }, "devDependencies": { - "@fabrix/fabrix": "^1.0.1", + "@fabrix/fabrix": "^1.0.6", "@fabrix/lint": "^1.0.0-alpha.3", "@fabrix/spool-router": "^1.0.0", "@fabrix/spool-tapestries": "^1.0.0", "@types/lodash": "^4.14.109", "@types/node": "~10.3.4", - "@types/sequelize": "^4.27.23", "mocha": "^5", "nyc": "^12.0.2", "pg": "^6.4.2", @@ -70,7 +70,7 @@ "typescript": "~2.8.1" }, "peerDependencies": { - "@fabrix/fabrix": "^1.0.1", + "@fabrix/fabrix": "^1.0.6", "@fabrix/spool-router": "^1.0.0" }, "engines": { diff --git a/test/app.js b/test/app.js index 9d164c5..6759ba3 100755 --- a/test/app.js +++ b/test/app.js @@ -3,8 +3,11 @@ const _ = require('lodash') const smokesignals = require('smokesignals') const testModel = require('./testmodel') +const SequelizeResolver = require('../dist').SequelizeResolver + +// require('@fabrix/fabrix') +const Model = require('@fabrix/fabrix/dist/common').FabrixModel -require('@fabrix/fabrix') const App = { pkg: { @@ -25,6 +28,10 @@ const App = { } } + static get resolver () { + return SequelizeResolver + } + associate(models) { models.Page.belongsTo(models.User, { as: 'Owner' @@ -44,6 +51,10 @@ const App = { } } + static get resolver () { + return SequelizeResolver + } + associate(models) { models.Project.belongsToMany(models.User, { through: models.UserProject @@ -58,6 +69,10 @@ const App = { status: Sequelize.STRING } } + + static get resolver () { + return SequelizeResolver + } }, User: class User extends Model { static config(app, Sequelize) { @@ -68,12 +83,19 @@ const App = { static schema(app, Sequelize) { return { - name: { type: Sequelize.STRING, allowNull: false}, + name: { + type: Sequelize.STRING, + allowNull: false + }, password: Sequelize.STRING, displayName: Sequelize.STRING } } + static get resolver () { + return SequelizeResolver + } + associate(models) { models.User.hasMany(models.Role, { as: 'roles', @@ -94,13 +116,14 @@ const App = { static schema(app, Sequelize) { return { - name: Sequelize.STRING /*, - user: { - model: 'User' - }*/ + name: 'string' } } + static get resolver () { + return SequelizeResolver + } + associate(models) { models.Role.belongsTo(models.User, { onDelete: 'CASCADE', @@ -169,6 +192,10 @@ const App = { afterValidate: Sequelize.INTEGER } } + + static get resolver () { + return SequelizeResolver + } }, testModel } diff --git a/test/integrations/spool.test.js b/test/integrations/spool.test.js index eb2328e..9283487 100755 --- a/test/integrations/spool.test.js +++ b/test/integrations/spool.test.js @@ -10,4 +10,26 @@ describe('Spool', () => { it('should be loaded into the app.spools collection', () => { assert(spool) }) + it('should create directly through app.models', (done) => { + global.app.models.Page.create({name: 'test'}) + .then(page => { + assert.equal(page.name, 'test') + done() + }) + .catch(err => { + done(err) + }) + }) + + it('should access a classLevelMethod', (done) => { + assert.equal(global.app.models.testModel.classLevelMethod(), 'foo') + assert.equal(global.app.models.testModel.resolver._sequelizeModel.classLevelMethod(), 'foo') + done() + }) + + it('should access a instanceLevelMethod', (done) => { + const instance = global.app.models.testModel.build({name: 'test'}) + assert.equal(instance.instanceLevelMethod(), 'bar') + done() + }) }) diff --git a/test/testmodel.js b/test/testmodel.js index 5bf1233..1cb3a7a 100644 --- a/test/testmodel.js +++ b/test/testmodel.js @@ -1,4 +1,9 @@ -require('@fabrix/fabrix') +const Model = require('@fabrix/fabrix/dist/common').FabrixModel +const SequelizeResolver = require('../dist').SequelizeResolver + +const TestResolver = class TestResolver extends SequelizeResolver { + +} module.exports = Test = class Test extends Model { static config(app, Sequelize) { @@ -13,6 +18,10 @@ module.exports = Test = class Test extends Model { } } + static get resolver () { + return TestResolver + } + classLevelMethod() { return 'foo' } From 33883bb6c261866c3d5f520b201cfc4c9f188cce Mon Sep 17 00:00:00 2001 From: scott-wyatt Date: Tue, 10 Jul 2018 20:34:45 -0400 Subject: [PATCH 2/2] [chore] cleanup --- lib/SequelizeSpool.ts | 13 +++++----- lib/transformer.ts | 60 ++++++++++++++++++++++++++----------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/lib/SequelizeSpool.ts b/lib/SequelizeSpool.ts index c998398..36d9250 100755 --- a/lib/SequelizeSpool.ts +++ b/lib/SequelizeSpool.ts @@ -57,19 +57,18 @@ export class SequelizeSpool extends DatastoreSpool { /** * Merge configuration into models, load Sequelize collections. */ - // configure() { - // - // } + configure() { + // Holds a collection of the connections made through Sequelize + this._connections = Transformer.getConnections(this.app) + // Holds a collection of the Sequelize models + this._models = Transformer.getModels(this.app, this.connections) + } /** * Initialize Sequelize. This will compile the schema and connect to the * database. */ async initialize() { - // Holds a collection of the connections made through Sequelize - this._connections = Transformer.getConnections(this.app) - // Holds a collection of the Sequelize models - this._models = Transformer.getModels(this.app, this.connections) // Migrate the connections and/or models by their migration strategy return this.migrate() } diff --git a/lib/transformer.ts b/lib/transformer.ts index 1abd240..4b94704 100755 --- a/lib/transformer.ts +++ b/lib/transformer.ts @@ -7,6 +7,9 @@ import { pickBy, isString, startsWith } from 'lodash' export const Transformer = { BreakException: {}, + /** + * Reserved Methods that the model shouldn't inherit from the resolver + */ reservedMethods: [ '_app', '_datastore', @@ -23,8 +26,11 @@ export const Transformer = { 'models', 'connect' ], - // Supplied by Model vs Recognized by Sequelize - // Generally Fabrix ORMS support: string, int, date + + /** + * Supplied by Model vs Recognized by Sequelize + * Generally Fabrix ORMS support: string, int, date + */ dataTypes: { '^(STRING|string)': 'STRING', '^(STRING|string)\((\w*)\)': 'STRING($2)', @@ -107,6 +113,24 @@ export const Transformer = { return schema }, + replaceDataType: (dataType) => { + let transformed + try { + Object.keys(Transformer.dataTypes).forEach(type => { + const exp = new RegExp(type) + if (exp.test(dataType)) { + transformed = Sequelize[dataType.replace(exp, Transformer.dataTypes[type])] + throw Transformer.BreakException + } + }) + } + catch (e) { + if (e !== Transformer.BreakException) { + throw e + } + } + return transformed + }, /** * Transforms Schema to Sequelize method if defined as a string * Common from Spools built for waterline @@ -115,21 +139,16 @@ export const Transformer = { const transformed: {[key: string]: any } = {} Object.keys(schema).forEach(s => { if (typeof schema[s] === 'string') { - try { - Object.keys(Transformer.dataTypes).forEach(type => { - const exp = new RegExp(type) - if (exp.test(schema[s])) { - transformed[s] = Sequelize[schema[s].replace(exp, Transformer.dataTypes[type])] - throw Transformer.BreakException - } - }) - } - catch (e) { - if (e !== Transformer.BreakException) { - throw e - } - } + transformed[s] = Transformer.replaceDataType(schema[s]) } + // else if ( + // typeof schema[s] === 'object' + // && schema[s].hasOwnProperty('type') + // && typeof schema[s].type === 'string' + // ) { + // schema[s].type = Transformer.replaceDataType(schema[s].type) + // transformed[s] = schema[s] + // } else { transformed[s] = schema[s] } @@ -160,15 +179,9 @@ export const Transformer = { const store = modelConfig.store || app.config.get('models.defaultStore') const connection = connections[store] const migrate = modelConfig.migrate || app.config.get('models.migrate') || connection.migrate - // const instanceMethods = Transformer.getModelPrototypes(model) - // const classMethods = Transformer.getModelMethods(model, instanceMethods) const options = Transformer.getModelOptions(app, model) const schema = Transformer.getModelSchema(app, model) - // const SequelizeModel = connection.define(modelName, schema, options) - // model.resolver.store = store - // model.resolver.migrate = migrate - // console.log('BROKE BEFORE', typeof model.resolver.create) model.store = store model.migrate = migrate model.resolver.connection = connection @@ -259,7 +272,8 @@ export const Transformer = { if (models[modelName].hasOwnProperty('associate')) { models[modelName].associate(sequelizeModels) } - models[modelName].associations = models[modelName].resolver.sequelizeModel.associations + // Convenience link between model.associations and the sequelize.model.associations + // models[modelName].associations = models[modelName].resolver.sequelizeModel.associations }) } }