diff --git a/lib/interfaces/ISequelizeConfig.ts b/lib/interfaces/ISequelizeConfig.ts index 7fde370e..ca5e7637 100644 --- a/lib/interfaces/ISequelizeConfig.ts +++ b/lib/interfaces/ISequelizeConfig.ts @@ -5,7 +5,7 @@ export interface ISequelizeConfig extends Options { /** * Name of database */ - name: string; + database: string; /** * Username of database diff --git a/lib/interfaces/ISequelizeDbNameConfig.ts b/lib/interfaces/ISequelizeDbNameConfig.ts new file mode 100644 index 00000000..eee2a218 --- /dev/null +++ b/lib/interfaces/ISequelizeDbNameConfig.ts @@ -0,0 +1,36 @@ +import {Options} from 'sequelize'; + +/** + * This class is dedicated for deprecated "name" property. + * For congruence to Sequelize config, use "database" instead. + */ +export interface ISequelizeDbNameConfig extends Options { + + /** + * Name of database + * @deprecated: name is deprecated. Use database instead. + */ + name: string; + + /** + * Username of database + */ + username: string; + + /** + * Password for database user + */ + password: string; + + /** + * Path to models, which should be loaded + */ + modelPaths?: string[]; + + /** + * Makes it possible to use sequelize for validation only + * if set to true. For this configuration it is always false. + * See ISequelizeValidationOnlyConfig interface + */ + validateOnly?: false; +} diff --git a/lib/interfaces/ISequelizeUriConfig.ts b/lib/interfaces/ISequelizeUriConfig.ts new file mode 100644 index 00000000..159f0dc3 --- /dev/null +++ b/lib/interfaces/ISequelizeUriConfig.ts @@ -0,0 +1,21 @@ +import {Options} from 'sequelize'; + +export interface ISequelizeUriConfig extends Options { + + /** + * Uri connection string to database + */ + uri: string; + + /** + * Path to models, which should be loaded + */ + modelPaths?: string[]; + + /** + * Makes it possible to use sequelize for validation only + * if set to true. For this configuration it is always false. + * See ISequelizeValidationOnlyConfig interface + */ + validateOnly?: false; +} diff --git a/lib/models/BaseSequelize.ts b/lib/models/BaseSequelize.ts index 5e68fdbd..ee143df8 100644 --- a/lib/models/BaseSequelize.ts +++ b/lib/models/BaseSequelize.ts @@ -2,6 +2,9 @@ import {Model} from "./Model"; import {DEFAULT_DEFINE_OPTIONS, getModels} from "../services/models"; import {getAssociations, processAssociation} from "../services/association"; import {ISequelizeConfig} from "../interfaces/ISequelizeConfig"; +import {ISequelizeUriConfig} from "../interfaces/ISequelizeUriConfig"; +import {ISequelizeDbNameConfig} from "../interfaces/ISequelizeDbNameConfig"; +import {SequelizeConfig} from "../types/SequelizeConfig"; import {resolveScopes} from "../services/scopes"; import {ISequelizeValidationOnlyConfig} from "../interfaces/ISequelizeValidationOnlyConfig"; import {extend} from "../utils/object"; @@ -19,6 +22,14 @@ export abstract class BaseSequelize { thoughMap: { [through: string]: any } = {}; _: { [modelName: string]: (typeof Model) } = {}; + static isISequelizeDbNameConfig(obj: any): obj is ISequelizeDbNameConfig { + return obj.hasOwnProperty("name") && obj.hasOwnProperty("username"); + } + + static isISequelizeUriConfig(obj: any): obj is ISequelizeUriConfig { + return obj.hasOwnProperty("uri"); + } + static extend(target: any): void { extend(target, this); @@ -27,7 +38,7 @@ export abstract class BaseSequelize { /** * Prepares sequelize config passed to original sequelize constructor */ - static prepareConfig(config: ISequelizeConfig | ISequelizeValidationOnlyConfig): ISequelizeConfig { + static prepareConfig(config: SequelizeConfig | ISequelizeValidationOnlyConfig): SequelizeConfig { if (!config.define) { config.define = {}; } @@ -37,13 +48,19 @@ export abstract class BaseSequelize { return this.getValidationOnlyConfig(config); } - return {...config as ISequelizeConfig}; + + if (BaseSequelize.isISequelizeDbNameConfig(config)) { + // @TODO: remove deprecated "name" property + return {...config, database: config.name} as ISequelizeConfig; + } + + return {...config as SequelizeConfig}; } - static getValidationOnlyConfig(config: ISequelizeConfig | ISequelizeValidationOnlyConfig): ISequelizeConfig { + static getValidationOnlyConfig(config: SequelizeConfig | ISequelizeValidationOnlyConfig): ISequelizeConfig { return { ...config, - name: '_name_', + database: '_name_', username: '_username_', password: '_password_', dialect: 'sqlite', @@ -64,7 +81,7 @@ export abstract class BaseSequelize { models.forEach(model => this._[model.name] = model); } - init(config: ISequelizeConfig): void { + init(config: SequelizeConfig): void { if (config.modelPaths) this.addModels(config.modelPaths); } diff --git a/lib/models/Sequelize.d.ts b/lib/models/Sequelize.d.ts index ecd30318..285a7fad 100644 --- a/lib/models/Sequelize.d.ts +++ b/lib/models/Sequelize.d.ts @@ -1,15 +1,15 @@ -/// import 'reflect-metadata'; import * as SequelizeOrigin from 'sequelize'; import {Model} from "./Model"; -import {ISequelizeConfig} from "../interfaces/ISequelizeConfig"; +import {SequelizeConfig} from "../types/SequelizeConfig"; import {ISequelizeValidationOnlyConfig} from "../interfaces/ISequelizeValidationOnlyConfig"; export declare class Sequelize extends SequelizeOrigin { _: {[modelName: string]: (typeof Model)}; - constructor(config: ISequelizeConfig | ISequelizeValidationOnlyConfig); + constructor(config: SequelizeConfig | ISequelizeValidationOnlyConfig); + constructor(uri: string); addModels(models: Array): void; addModels(modelPaths: string[]): void; diff --git a/lib/models/v3/Sequelize.ts b/lib/models/v3/Sequelize.ts index 331f58f2..837f3193 100644 --- a/lib/models/v3/Sequelize.ts +++ b/lib/models/v3/Sequelize.ts @@ -1,15 +1,13 @@ import 'reflect-metadata'; import * as SequelizeOrigin from 'sequelize'; import {Model} from "../Model"; -import {ISequelizeConfig} from "../../interfaces/ISequelizeConfig"; +import {SequelizeConfig} from "../../types/SequelizeConfig"; import {getModelName, getAttributes, getOptions} from "../../services/models"; import {PROPERTY_LINK_TO_ORIG} from "../../services/models"; import {BaseSequelize} from "../BaseSequelize"; import {Table} from "../../annotations/Table"; import {ISequelizeAssociation} from "../../interfaces/ISequelizeAssociation"; -let preparedConfig; - export class Sequelize extends SequelizeOrigin implements BaseSequelize { // to fix "$1" called with something that's not an instance of Sequelize.Model @@ -17,24 +15,27 @@ export class Sequelize extends SequelizeOrigin implements BaseSequelize { thoughMap: { [through: string]: any } = {}; _: { [modelName: string]: typeof Model } = {}; - init: (config: ISequelizeConfig) => void; + init: (config: SequelizeConfig) => void; addModels: (models: Array | string[]) => void; associateModels: (models: Array) => void; - constructor(config: ISequelizeConfig) { - // a spread operator would be the more reasonable approach here, - // but this is currently not possible due to a bug by ts - // https://github.com/Microsoft/TypeScript/issues/4130 - // TODO@robin probably make the constructor private and - // TODO use a static factory function instead + constructor(config: SequelizeConfig | string) { + super( - (preparedConfig = BaseSequelize.prepareConfig(config), preparedConfig.name), - preparedConfig.username, - preparedConfig.password, - preparedConfig + (typeof config === "string") ? + config : // URI string + BaseSequelize.isISequelizeUriConfig(config) ? + config.uri : // URI string from ISequelizeUriConfig + BaseSequelize.prepareConfig(config) // Config object (ISequelizeConfig) ); - this.init(config); + if (BaseSequelize.isISequelizeUriConfig(config)) { + this.options = {...this.options, ...config}; + } + + if (typeof config !== "string") { + this.init(config); + } } getThroughModel(through: string): typeof Model { diff --git a/lib/models/v4/Sequelize.ts b/lib/models/v4/Sequelize.ts index f1cd89ad..cfa6f645 100644 --- a/lib/models/v4/Sequelize.ts +++ b/lib/models/v4/Sequelize.ts @@ -1,36 +1,37 @@ import 'reflect-metadata'; import * as OriginSequelize from 'sequelize'; import {Model} from "../Model"; -import {ISequelizeConfig} from "../../interfaces/ISequelizeConfig"; +import {SequelizeConfig} from "../../types/SequelizeConfig"; import {getModelName, getAttributes, getOptions} from "../../services/models"; import {BaseSequelize} from "../BaseSequelize"; import {Table} from "../../annotations/Table"; import {ISequelizeAssociation} from "../../interfaces/ISequelizeAssociation"; -let preparedConfig; - export class Sequelize extends OriginSequelize implements BaseSequelize { thoughMap: {[through: string]: any} = {}; _: {[modelName: string]: typeof Model} = {}; - init: (config: ISequelizeConfig) => void; - addModels: (models: Array|string[]) => void; + init: (config: SequelizeConfig) => void; + addModels: (models: Array | string[]) => void; associateModels: (models: Array) => void; - constructor(config: ISequelizeConfig) { - // a spread operator would be the more reasonable approach here, - // but this is currently not possible due to a bug by ts - // https://github.com/Microsoft/TypeScript/issues/4130 - // TODO@robin probably make the constructor private and - // TODO use a static factory function instead + constructor(config: SequelizeConfig | string) { + super( - (preparedConfig = BaseSequelize.prepareConfig(config), preparedConfig.name), - preparedConfig.username, - preparedConfig.password, - preparedConfig + (typeof config === "string") ? + config : // URI string + BaseSequelize.isISequelizeUriConfig(config) ? + config.uri : // URI string from ISequelizeUriConfig + BaseSequelize.prepareConfig(config) // Config object (ISequelizeConfig) ); - this.init(config); + if (BaseSequelize.isISequelizeUriConfig(config)) { + this.options = {...this.options, ...config}; + } + + if (typeof config !== "string") { + this.init(config); + } } getThroughModel(through: string): typeof Model { diff --git a/lib/types/SequelizeConfig.ts b/lib/types/SequelizeConfig.ts new file mode 100644 index 00000000..182633e8 --- /dev/null +++ b/lib/types/SequelizeConfig.ts @@ -0,0 +1,5 @@ +import {ISequelizeConfig} from "../interfaces/ISequelizeConfig"; +import {ISequelizeUriConfig} from "../interfaces/ISequelizeUriConfig"; +import {ISequelizeDbNameConfig} from "../interfaces/ISequelizeDbNameConfig"; + +export type SequelizeConfig = ISequelizeConfig | ISequelizeUriConfig | ISequelizeDbNameConfig; diff --git a/package-lock.json b/package-lock.json index 6017e5b1..d2f82403 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@types/bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha1-PH6M9mC51g6iXHh/3SWN22q7OE0=" + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha1-8SAWKwT9bVXhA0bX4ulu7UYrAs8=" }, "@types/chai": { "version": "3.4.35", @@ -68,13 +68,13 @@ "integrity": "sha1-tkd8qal+UmXyrGf56nBOrl4Or00=" }, "@types/sequelize": { - "version": "4.0.68", - "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.0.68.tgz", - "integrity": "sha512-rarqeMu6s9wbdUxGG16erywZGKrrX4fzgvckpd+88s3EqPKPjTNCyEGzStA8q4mM7avHA/LMxnwBro1+oXKfYw==", + "version": "4.0.69", + "resolved": "https://registry.npmjs.org/@types/sequelize/-/sequelize-4.0.69.tgz", + "integrity": "sha512-uOfvMCj3n25GiYk7GQtxaTCD4NcY69EB5H+TwlHkcaJ1l2Wz8rjYnfJMLpen+Jv4rpMSX5dbmcrPRIiSdYeCMg==", "requires": { - "@types/bluebird": "3.5.5", + "@types/bluebird": "3.5.4", "@types/lodash": "4.14.54", - "@types/validator": "6.2.0" + "@types/validator": "6.2.2" } }, "@types/sinon": { @@ -94,9 +94,9 @@ } }, "@types/validator": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-6.2.0.tgz", - "integrity": "sha1-AgMi/hkp9piJ62daG9tamDlLcfA=" + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-6.2.2.tgz", + "integrity": "sha512-ucoZO5gK7SJnMFrXLiMboPRnG9KwYN3PleaFc4uoEw/Ejrs8+osBs5XRDNjHfa5wFwc1jvFnn+rEe4DsRkwT8g==" }, "ansi-align": { "version": "1.1.0", @@ -837,7 +837,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=", + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", "dev": true }, "imurmurhash": { @@ -1135,7 +1135,7 @@ "lru-cache": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", "dev": true, "requires": { "pseudomap": "1.0.2", @@ -1166,7 +1166,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.7" @@ -1269,7 +1269,7 @@ "readable-stream": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.11.tgz", - "integrity": "sha1-B5azH412iAB/8Lk6gIjTSqF8D3I=", + "integrity": "sha512-h+8+r3MKEhkiVrwdKL8aWs1oc1VvBu33ueshOvS26RsZQ3Amhx/oO3TKe4lApSV9ueY6as8EAh7mtuFjdlhg9Q==", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -1292,7 +1292,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { "safe-buffer": "5.1.0" @@ -1338,7 +1338,7 @@ "nyc": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.0.2.tgz", - "integrity": "sha1-nlkqaXGGAoJTtmhRbDjwecOcCPM=", + "integrity": "sha512-31rRd6ME9NM17w0oPKqi51a6fzJAqYarnzQXK+iL8XaX+3H6VH0BQut7qHIgrv2mBASRic4oNi2KRgcbFODrsQ==", "dev": true, "requires": { "archy": "1.0.0", @@ -4557,7 +4557,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", diff --git a/package.json b/package.json index 046d72d7..5b7d01ee 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,10 @@ "main": "index.js", "types": "index.d.ts", "dependencies": { + "@types/bluebird": "3.5.4", "@types/node": "6.0.41", "@types/reflect-metadata": "0.0.4", - "@types/sequelize": "4.0.68", + "@types/sequelize": "4.0.69", "es6-shim": "0.35.3" }, "devDependencies": { diff --git a/test/specs/models/sequelize.spec.ts b/test/specs/models/sequelize.spec.ts index 67eaaa0a..45d611fa 100644 --- a/test/specs/models/sequelize.spec.ts +++ b/test/specs/models/sequelize.spec.ts @@ -11,6 +11,14 @@ import {Table} from '../../../lib/annotations/Table'; describe('sequelize', () => { const sequelize = createSequelize(false); + const connectionUri = "sqlite://root@localhost/__"; + + function testOptionsProp(instance: Sequelize): void { + expect(instance).to.have.property('options').that.have.property('dialect').that.eqls('sqlite'); + expect(instance).to.have.property('config').that.have.property('host').that.eqls('localhost'); + expect(instance).to.have.property('config').that.have.property('database').that.eqls('__'); + expect(instance).to.have.property('config').that.have.property('username').that.eqls('root'); + } describe('constructor', () => { @@ -20,6 +28,72 @@ describe('sequelize', () => { }); + describe('constructor: using "name" property as a db name', () => { + + const db = '__'; + const sequelizeDbName = new Sequelize({ + name: db, + dialect: 'sqlite', + username: 'root', + password: '', + storage: ':memory:', + logging: !('SEQ_SILENT' in process.env) + }); + + it('should equal Sequelize class', () => { + expect(sequelizeDbName.constructor).to.equal(Sequelize); + }); + + it('should contain database property, which equal to db.', () => { + expect(sequelizeDbName) + .to.have.property('config') + .that.have.property('database') + .that.eqls(db); + }); + + }); + + describe('constructor using uri in options object', () => { + + const sequelizeUri = new Sequelize({ + uri: connectionUri, + storage: ':memory:', + logging: !('SEQ_SILENT' in process.env), + pool: {max: 8, min: 0} + }); + + it('should equal Sequelize class', () => { + expect(sequelizeUri.constructor).to.equal(Sequelize); + }); + + it('should contain valid options extracted from connection string', () => { + testOptionsProp(sequelizeUri); + }); + + it('should contain additional Sequelize options', () => { + expect(sequelizeUri) + .to.have.property('options') + .that.have.property('pool') + .that.have.property('max') + .that.eqls(8); + }); + + }); + + describe('constructor using uri string', () => { + + const sequelizeUri = new Sequelize(connectionUri); + + it('should equal Sequelize class', () => { + expect(sequelizeUri.constructor).to.equal(Sequelize); + }); + + it('should contain valid options extracted from connection string', () => { + testOptionsProp(sequelizeUri); + }); + + }); + describe('global define options', () => { const DEFINE_OPTIONS = {timestamps: true, underscoredAll: true}; diff --git a/test/utils/sequelize.ts b/test/utils/sequelize.ts index 5fc4dcfb..c740ad61 100644 --- a/test/utils/sequelize.ts +++ b/test/utils/sequelize.ts @@ -5,7 +5,7 @@ import {DefineOptions, Sequelize as SequelizeType} from "sequelize"; export function createSequelize(useModelsInPath: boolean = true, define: DefineOptions = {}): Sequelize { return new Sequelize({ - name: '__', + database: '__', dialect: 'sqlite', username: 'root', password: '',