// @flow

import Sequelize, {Model, Transaction} from "sequelize";
import type {fn} from 'sequelize'
import type { DefineAttributes } from "sequelize";
import type {
  HasOneGetOne,
  HasOneSetOne,
  HasOneCreateOne,
  HasManyCreateOne,
  HasManyRemoveOne,
  BelongsToGetOne,
  BelongsToSetOne,
  BelongsToCreateOne,
  BelongsToManyCreateOne,
  HasManyGetMany,
  HasManySetMany,
  HasManyAddMany,
  HasManyAddOne,
  HasManyRemoveMany,
  HasManyHasOne,
  HasManyHasMany,
  HasManyCount,
  BelongsToManyGetMany,
  BelongsToManySetMany,
  BelongsToManyAddMany,
  BelongsToManyAddOne,
  BelongsToManyRemoveOne,
  BelongsToManyRemoveMany,
  BelongsToManyHasOne,
  BelongsToManyHasMany,
  BelongsToManyCount,
  Association,
  DataTypeAbstract,
} from "sequelize";

//
//  Fixtures
// ~~~~~~~~~~
//

type AnyAttributes = Object;

let s         = new Sequelize( '' );
let sequelize = s;
let DataTypes = Sequelize;
let User: Class<Model<AnyAttributes>>      = s.define( 'user', {} );
let user      = User.build();
let Task: Class<Model<AnyAttributes>>      = s.define( 'task', {} );
let Group: Class<Model<AnyAttributes>>     = s.define( 'group', {} );
let Comment: Class<Model<AnyAttributes>>   = s.define( 'comment', {} );
let Post: Class<Model<AnyAttributes>>      = s.define( 'post', {} );
let t: Transaction = (null: any);
s.transaction().then( ( a ) => t = a );

//
//  Generics
// ~~~~~~~~~~
//

interface GUserAttributes {
  id? : number;
  username? : string;
}

declare class GUserModel extends Model<GUserAttributes> {}
let GUser: typeof GUserModel = s.define( 'user', { id: Sequelize.INTEGER, username : Sequelize.STRING });
GUser.create({ id : 1, username : 'one' }).then( ( guser ) => guser.save() );

let schema : DefineAttributes = {
  key : { type : Sequelize.STRING, primaryKey : true },
  value : Sequelize.STRING
};

s.define('user', schema);

interface GTaskAttributes {
  revision? : number;
  name? : string;
}
declare class GTaskModel extends Model<GTaskAttributes> {
  upRevision(): void;
}
let GTask: typeof GTaskModel = s.define( 'task', { revision : Sequelize.INTEGER, name : Sequelize.STRING });

GUser.hasMany(GTask);

GTask.create({ revision: 1, name: 'test' }).then( (gtask) => gtask.upRevision() );

//
//  Model.Options
// ~~~~~~~~~~~~~~~
//

(GUser.options.name.singular: string);
(GUser.options.name.plural: string);
(GUser.options.timestamps: ?boolean);
(GUser.options.paranoid: ?boolean);
(GUser.options.underscored: ?boolean);
(GUser.options.underscoredAll: ?boolean);
(GUser.options.hasTrigger: ?boolean);
(GUser.options.freezeTableName: ?boolean);

//
//  Associations
// ~~~~~~~~~~~~~~
//
//  https://github.com/sequelize/sequelize/tree/v3.4.1/test/integration/associations
//

User.hasOne( Task );
User.hasOne( Task, { foreignKey : 'primaryGroupId', as : 'primaryUsers' } );
User.hasOne( Task, { foreignKey : 'userCoolIdTag' } );
User.hasOne( Task, { foreignKey : 'userId', keyType : Sequelize.STRING, constraints : false } );
Task.hasOne( User, { foreignKey : { name : 'taskId', field : 'task_id' } } );
User.hasOne( Task, { foreignKey : { name : 'uid', allowNull : false } } );
User.hasOne( Task, { onDelete : 'cascade' } );
User.hasOne( Task, { onUpdate : 'cascade' } );
User.hasOne( Task, { onDelete : 'cascade', hooks : true } );
User.hasOne( Task, { foreignKey : { allowNull : false } } );
User.hasOne( Task, { foreignKeyConstraint : true } );

User.belongsTo( Task );
User.belongsTo( Task, { foreignKey : 'primaryGroupId', as : 'primaryUsers' } );
Task.belongsTo( User, { foreignKey : 'user_id' } );
Task.belongsTo( User, { foreignKey : 'user_name', targetKey : 'username' } );
User.belongsTo( User, { foreignKey : 'userId', keyType : Sequelize.STRING, constraints : false } );
User.belongsTo( Post, { foreignKey : { name : 'AccountId', field : 'account_id' } } );
Task.belongsTo( User, { foreignKey : { allowNull : false, name : 'uid' } } );
Task.belongsTo( User, { constraints : false } );
Task.belongsTo( User, { onDelete : 'cascade' } );
Task.belongsTo( User, { onUpdate : 'restrict' } );
User.belongsTo( User, {
  as : 'parentBlocks',
  foreignKey : 'child',
  foreignKeyConstraint : true
} );

User.hasMany( User );
User.hasMany( User, { foreignKey : 'primaryGroupId', as : 'primaryUsers' } );
User.hasMany( Task, { foreignKey : 'userId' } );
User.hasMany( Task, { foreignKey : 'userId', as : 'activeTasks', scope : { active : true } } );
User.hasMany( Task, { foreignKey : 'userId', keyType : Sequelize.STRING, constraints : false } );
User.hasMany( Task, { foreignKey : { name : 'uid', allowNull : false } } );
User.hasMany( Task, { foreignKey : { allowNull : true } } );
User.hasMany( Task, { as : 'Children' } );
User.hasMany( Task, { as : { singular : 'task', plural : 'taskz' } } );
User.hasMany( Task, { constraints : false } );
User.hasMany( Task, { onDelete : 'cascade' } );
User.hasMany( Task, { onUpdate : 'cascade' } );
Post.hasMany( Task, { foreignKey : 'commentable_id', scope : { commentable : 'post' } } );
User.hasMany( User, {
  as : 'childBlocks',
  foreignKey : 'parent',
  foreignKeyConstraint : true
} );

User.belongsToMany( Task, { through : 'UserTasks' } );
User.belongsToMany( User, { through : Task } );
User.belongsToMany( Group, { as : 'groups', through : Task, foreignKey : 'id_user' } );
User.belongsToMany( Task, { as : 'activeTasks', through : Task, scope : { active : true } } );
User.belongsToMany( Task, { as : 'startedTasks', through : { model : Task, scope : { started : true } } } );
User.belongsToMany( Group, { through : 'group_members', foreignKey : 'group_id', otherKey : 'member_id' } );
User.belongsToMany( User, { as : 'Participants', through : User } );
User.belongsToMany( Group, { through : 'user_places', foreignKey : 'user_id' } );
User.belongsToMany( Group, {
  through : 'user_projects',
  as : 'Projects',
  foreignKey : {
    field : 'user_id',
    name : 'userId'
  },
  otherKey : {
    field : 'project_id',
    name : 'projectId'
  }
} );
User.belongsToMany( Task, { onDelete : 'RESTRICT', through : 'tasksusers' } );
User.belongsToMany( Task, { constraints : false, through : 'tasksusers' } );
User.belongsToMany( Task, { foreignKey : { name : 'user_id', defaultValue : 42 }, through : 'UserProjects' } );
User.belongsToMany( Post, { through : User } );
Post.belongsToMany( User, { as : 'categories', through : User, scope : { type : 'category' } } );
Post.belongsToMany( User, { as : 'tags', through : User, scope : { type : 'tag' } } );
Post.belongsToMany( User, {
  through : {
    model : User,
    unique : false,
    scope : {
      taggable : 'post'
    }
  },
  foreignKey : 'taggable_id',
  constraints : false
} );
Post.belongsToMany( Post, { through : { model : Post, unique : false }, foreignKey : 'tag_id' } );
Post.belongsToMany( Post, { as : 'Parents', through : 'Family', foreignKey : 'ChildId', otherKey : 'PersonId' } );

//
// Mixins
// ~~~~~~
//
//  https://github.com/sequelize/sequelize/tree/v3.4.1/test/integration/associations
//

let Product: typeof ProductInstance = s.define('product', {});
let product = Product.build();

let Barcode: typeof BarcodeInstance = s.define('barcode', {});
let barcode = Barcode.build();

let Warehouse: typeof WarehouseInstance = s.define('warehouse', {});
let warehouse = Warehouse.build();

let Branch: typeof BranchInstance = s.define('branch', {});
let branch = Branch.build();

let WarehouseBranch: typeof WarehouseBranchInstance = s.define('warehouseBranch', {});

let Customer: typeof CustomerInstance = s.define('customer', {});
let customer = Customer.build();

const ProductBarcode = Product.hasOne(Barcode);
const BarcodeProduct = Barcode.belongsTo(Product);

const WarehouseProducts = Warehouse.hasMany(Product);
const ProductWarehouse = Product.belongsTo(Warehouse);

const WarehouseBranches = Warehouse.belongsToMany(Branch, { through: WarehouseBranch });
const BranchWarehouses = Branch.belongsToMany(Warehouse, { through: WarehouseBranch });

Branch.belongsToMany(Customer, { through: 'branchCustomer' });
Customer.belongsToMany(Branch, { through: 'branchCustomer' });

// Metadata

(Warehouse.associations.branch: Association<WarehouseInstance, BranchInstance>);
(Warehouse.tableName: string);
(Warehouse.rawAttributes.blah.type: DataTypeAbstract);
(Warehouse.tableAttributes.blah.type: DataTypeAbstract);
(Warehouse.attributes.blah.type: DataTypeAbstract);
(Warehouse.primaryKeys.blah.type: DataTypeAbstract);

Warehouse.getAssociations(WarehouseBranch)
Warehouse.getAssociationForAlias(WarehouseBranch, 'blah')

// hasOne
product.getBarcode();
product.getBarcode({ scope: null }).then(b => b && b.code);

product.setBarcode(null);
product.setBarcode(1);
product.setBarcode(barcode);
product.setBarcode(barcode, { save: true }).then(() => { });

product.createBarcode();
product.createBarcode({ id: 1, code: '1434-2' });
product.createBarcode({ id: 1 }, { save: true, silent: true }).then(() => { });

// belongsTo
barcode.getProduct();
barcode.getProduct({ scope: 'foo' }).then(p => p && p.name);

barcode.setProduct(null);
barcode.setProduct(1);
barcode.setProduct(product);
barcode.setProduct(product, { save: true }).then(() => { });

barcode.createProduct({ id: 1, name: 'Crowbar' });
barcode.createProduct({ id: 1 }, { save: true, silent: true }).then((product) => { });

product.getWarehouse();
product.getWarehouse({ scope: null }).then(w => w && w.capacity);

product.setWarehouse(null);
product.setWarehouse(1);
product.setWarehouse(warehouse);
product.setWarehouse(warehouse, { save: true }).then(() => { });

product.createWarehouse();
product.createWarehouse({ id: 1, capacity: 10000 });
product.createWarehouse({ id: 1 }, { save: true, silent: true }).then(() => { });

// hasMany
warehouse.getProducts();
warehouse.getProducts({ where: {}, scope: false });
warehouse.getProducts({ where: {}, scope: false }).then((products) => products[0].id);

warehouse.setProducts(null);
warehouse.setProducts([product]);
warehouse.setProducts([product], { validate: true }).then(() => { });

warehouse.addProducts([product]);
warehouse.addProducts([product, 2], { validate: false }).then(() => { });

warehouse.addProduct(product);
warehouse.addProduct(2, { validate: true }).then(() => { });

warehouse.createProduct({ id: 1, name: 'baz' });
warehouse.createProduct({ id: 1 }, { silent: true }).then((product) => { });

warehouse.removeProducts();
warehouse.removeProducts([product]);
warehouse.removeProducts([product, 2], { validate: false }).then(() => { });

warehouse.removeProduct(product);
warehouse.removeProduct(2, { validate: true }).then(() => { });

warehouse.hasProducts([product]);
warehouse.hasProducts([product, 2], { scope: 'bar' }).then((result: boolean) => { });

warehouse.hasProduct(product);
warehouse.hasProduct(2, { scope: 'baz' }).then((result: boolean) => { });

warehouse.countProducts();
warehouse.countProducts({ scope: 'baz' }).then((result: number) => { });

// belongsToMany <Model>
warehouse.getBranches();
warehouse.getBranches({ where: {} });
warehouse.getBranches({ where: {} }).then((branches) => branches[0].rank);

warehouse.setBranches(null);
warehouse.setBranches([branch]);
warehouse.setBranches([branch, 2], { validate: true, through: { distance: 1 } }).then(() => { });

warehouse.addBranches([branch]);
warehouse.addBranches([branch, 2], { validate: false, through: { distance: 1 } }).then(() => { });

warehouse.addBranch(branch);
warehouse.addBranch(2, { validate: true, through: { distance: 1 } }).then(() => { });

warehouse.createBranch();
warehouse.createBranch({ id: 1, address: 'baz' });
warehouse.createBranch({ id: 1 }, { silent: true, through: { distance: 1 } }).then((branch) => { });

warehouse.removeBranches();
warehouse.removeBranches([branch]);
warehouse.removeBranches([branch, 2], { validate: false }).then(() => { });

warehouse.removeBranch(branch);
warehouse.removeBranch(2, { validate: true }).then(() => { });

warehouse.hasBranches([branch]);
warehouse.hasBranches([branch, 2], { scope: 'bar' }).then((result: boolean) => { });

warehouse.hasBranch(branch);
warehouse.hasBranch(2, { scope: 'baz' }).then((result: boolean) => { });

warehouse.countBranches();
warehouse.countBranches({ scope: 'baz' }).then((result: number) => { });

// belongsToMany <void>
customer.getBranches();
customer.getBranches({ where: {} });
customer.getBranches({ where: {} }).then((branches) => branches[0].rank);

customer.setBranches(null);
customer.setBranches([branch]);
customer.setBranches([branch, 2], { validate: true }).then(() => { });

customer.addBranches([branch]);
customer.addBranches([branch, 2], { validate: false }).then(() => { });

customer.addBranch(branch);
customer.addBranch(2, { validate: true }).then(() => { });

customer.createBranch();
customer.createBranch({ id: 1, address: 'baz' });
customer.createBranch({ id: 1 }, { silent: true }).then((branch) => { });

customer.removeBranches();
customer.removeBranches([branch]);
customer.removeBranches([branch, 2], { validate: false }).then(() => { });

customer.removeBranch(branch);
customer.removeBranch(2, { validate: true }).then(() => { });

customer.hasBranches([branch]);
customer.hasBranches([branch, 2], { scope: 'bar' }).then((result: boolean) => { });

customer.hasBranch(branch);
customer.hasBranch(2, { scope: 'baz' }).then((result: boolean) => { });

customer.countBranches();
customer.countBranches({ scope: 'baz' }).then((result: number) => { });

// query on associations

WarehouseProducts.get(warehouse).then((products: Array<ProductInstance>) => {})
WarehouseProducts.get([warehouse], {scope: 'baz'}).then((products: { [id: number]: ProductInstance, ... }) => {})
WarehouseProducts.count(warehouse).then((count: number) => {})
WarehouseProducts.count(warehouse, {scope: 'baz'}).then((count: number) => {})
WarehouseProducts.has(warehouse, [product, 2]).then((result: boolean) => {})
WarehouseProducts.has(warehouse, [product, 2], {scope: 'baz'}).then((result: boolean) => {})
WarehouseProducts.set(warehouse, [product, 2]).then((result: WarehouseInstance) => {})
WarehouseProducts.set(warehouse, [product, 2], {where: {}, scope: 'baz'}).then((result: WarehouseInstance) => {})
WarehouseProducts.remove(warehouse, [product, 2]).then((result: typeof WarehouseProducts) => {})
WarehouseProducts.remove(warehouse, [product, 2], {where: {}, individualHooks: true}).then((result: typeof WarehouseProducts) => {})
WarehouseProducts.create(warehouse, {
  name: 'doodad',
  price: 2.50,
}, {individualHooks: true}).then((result: ProductInstance) => {})

WarehouseBranches.get(warehouse).then((branches: Array<BranchInstance>) => {})
WarehouseBranches.get(warehouse, {scope: 'baz'}).then((branches: Array<BranchInstance>) => {})
WarehouseBranches.count(warehouse).then((count: number) => {})
WarehouseBranches.count(warehouse, {scope: 'baz'}).then((count: number) => {})
WarehouseBranches.has(warehouse, [branch, 2]).then((result: boolean) => {})
WarehouseBranches.has(warehouse, [branch, 2], {scope: 'blah'}).then((result: boolean) => {})
WarehouseBranches.set(warehouse, [branch, 2]).then((result: any) => {})
WarehouseBranches.set(warehouse, [branch, 2], {scope: 'blah'}).then((result: any) => {})
WarehouseBranches.remove(warehouse, [branch, 2]).then(() => {})
WarehouseBranches.remove(warehouse, [branch, 2], {scope: 'blah'}).then(() => {})
WarehouseBranches.create(warehouse, {
  address: 'somewhere',
  rank: 12,
}, {
  through: {
    distance: 4.5,
  },
  individualHooks: true,
}).then((result: Branch) => {})

ProductBarcode.get(product).then((barcode: BarcodeInstance) => {})
ProductBarcode.get(product).then((barcode: BarcodeInstance) => {})
ProductBarcode.get([product]).then((barcodes: { [id: number]: BarcodeInstance, ... }) => {})
ProductBarcode.get([product], {scope: 'boo'})
ProductBarcode.set(product, barcode)
ProductBarcode.set(product, 1, {scope: 'bar'})
ProductBarcode.create(product, {
  code: 'blah',
  dateIssued: new Date(),
}).then((barcode: BarcodeInstance) => {})
ProductBarcode.create(product, {
  code: 'blah',
  dateIssued: new Date(),
}, {
  save: true,
})

ProductWarehouse.get(product).then((warehouse: WarehouseInstance) => {})
ProductWarehouse.get([product]).then((warehouses: { [id: number]: WarehouseInstance, ... }) => {})
ProductWarehouse.get([product], {scope: 'boo'})
ProductWarehouse.set(product, warehouse)
ProductWarehouse.set(product, 1, {scope: 'bar'})
ProductWarehouse.create(product, {
  address: 'somewhere',
  capacity: 10000,
}).then((warehouse: WarehouseInstance) => {})
ProductWarehouse.create(product, {
  address: 'somewhere',
  capacity: 10000,
}, {
  save: true,
})

// query with associations

Warehouse.findAll({include: [{association: WarehouseProducts}]}).then((warehouses: Array<WarehouseInstance>) => {})
Warehouse.findAll({include: [{association: WarehouseBranches}]}).then((warehouses: Array<WarehouseInstance>) => {})
Branch.findAll({include: [{association: BranchWarehouses}]}).then((branch: Array<BranchInstance>) => {})
Product.findOne({include: [{association: ProductWarehouse}]}).then((product: ?Product) => {})

type ProductAttributes = {
  id?: number,
  name?: string,
  price?: number,
  ...
};

declare class ProductInstance extends Model<ProductAttributes> {
  id?: number;
  name?: string;
  price?: number;

  // hasOne association mixins:
  getBarcode: HasOneGetOne<BarcodeInstance>;
  setBarcode: HasOneSetOne<BarcodeInstance, number>;
  createBarcode: HasOneCreateOne<BarcodeAttributes>;

  // belongsTo association mixins:
  getWarehouse: BelongsToGetOne<WarehouseInstance>;
  setWarehouse: BelongsToSetOne<WarehouseInstance, number>;
  createWarehouse: BelongsToCreateOne<WarehouseAttributes>;
};

type BarcodeAttributes = {
  id?: number,
  code?: string,
  dateIssued?: Date,
  ...
};

declare class BarcodeInstance extends Model<BarcodeAttributes> {
  id?: number;
  code?: string;
  dateIssued?: Date;
  // belongsTo association mixins:
  getProduct: BelongsToGetOne<ProductInstance>;
  setProduct: BelongsToSetOne<ProductInstance, number>;
  createProduct: BelongsToCreateOne<ProductAttributes>;
};

type WarehouseAttributes = {
  id?: number,
  address?: string,
  capacity?: number,
  ...
};

declare class WarehouseInstance extends Model<WarehouseAttributes> {
  id?: number;
  address?: string;
  capacity?: number;
  // hasMany association mixins:
  getProducts: HasManyGetMany<ProductInstance>;
  setProducts: HasManySetMany<ProductInstance, number>;
  addProducts: HasManyAddMany<ProductInstance, number>;
  addProduct: HasManyAddOne<ProductInstance, number>;
  createProduct: HasManyCreateOne<ProductAttributes, ProductInstance>;
  removeProduct: HasManyRemoveOne<ProductInstance, number>;
  removeProducts: HasManyRemoveMany<ProductInstance, number>;
  hasProduct: HasManyHasOne<ProductInstance, number>;
  hasProducts: HasManyHasMany<ProductInstance, number>;
  countProducts: HasManyCount;

  // belongsToMany association mixins:
  getBranches: BelongsToManyGetMany<BranchInstance>;
  setBranches: BelongsToManySetMany<BranchInstance, number, WarehouseBranchAttributes>;
  addBranches: BelongsToManyAddMany<BranchInstance, number, WarehouseBranchAttributes>;
  addBranch: BelongsToManyAddOne<BranchInstance, number, WarehouseBranchAttributes>;
  createBranch: BelongsToManyCreateOne<BranchAttributes, BranchInstance, WarehouseBranchAttributes>;
  removeBranch: BelongsToManyRemoveOne<BranchInstance, number>;
  removeBranches: BelongsToManyRemoveMany<BranchInstance, number>;
  hasBranch: BelongsToManyHasOne<BranchInstance, number>;
  hasBranches: BelongsToManyHasMany<BranchInstance, number>;
  countBranches: BelongsToManyCount;
};

type BranchAttributes = {
  id?: number,
  address?: string,
  rank?: number,
  ...
};

declare class BranchInstance extends Model<BranchAttributes> {
  id?: number;
  address?: string;
  rank?: number;

  // belongsToMany association mixins:
  getWarehouses: BelongsToManyGetMany<WarehouseInstance>;
  setWarehouses: BelongsToManySetMany<WarehouseInstance, number, WarehouseBranchAttributes>;
  addWarehouses: BelongsToManyAddMany<WarehouseInstance, number, WarehouseBranchAttributes>;
  addWarehouse: BelongsToManyAddOne<WarehouseInstance, number, WarehouseBranchAttributes>;
  createWarehouse: BelongsToManyCreateOne<WarehouseAttributes, WarehouseInstance, WarehouseBranchAttributes>;
  removeWarehouse: BelongsToManyRemoveOne<WarehouseInstance, number>;
  removeWarehouses: BelongsToManyRemoveMany<WarehouseInstance, number>;
  hasWarehouse: BelongsToManyHasOne<WarehouseInstance, number>;
  hasWarehouses: BelongsToManyHasMany<WarehouseInstance, number>;
  countWarehouses: BelongsToManyCount;

  // belongsToMany association mixins:
  getCustomers: BelongsToManyGetMany<CustomerInstance>;
  setCustomers: BelongsToManySetMany<CustomerInstance, number, void>;
  addCustomers: BelongsToManyAddMany<CustomerInstance, number, void>;
  addCustomer: BelongsToManyAddOne<CustomerInstance, number, void>;
  createCustomer: BelongsToManyCreateOne<CustomerAttributes, CustomerInstance, void>;
  removeCustomer: BelongsToManyRemoveOne<CustomerInstance, number>;
  removeCustomers: BelongsToManyRemoveMany<CustomerInstance, number>;
  hasCustomer: BelongsToManyHasOne<CustomerInstance, number>;
  hasCustomers: BelongsToManyHasMany<CustomerInstance, number>;
  countCustomers: BelongsToManyCount;
};

type WarehouseBranchAttributes = { distance?: number, ... };

declare class WarehouseBranchInstance extends Model<WarehouseBranchAttributes> {
  distance?: number;
};

type CustomerAttributes = {
  id?: number,
  fullname?: string,
  credit?: number,
  ...
};

declare class CustomerInstance extends Model<CustomerAttributes> {
  id?: number;
  fullname?: string;
  credit?: number;
  // belongsToMany association mixins:
  getBranches: BelongsToManyGetMany<BranchInstance>;
  setBranches: BelongsToManySetMany<BranchInstance, number, void>;
  addBranches: BelongsToManyAddMany<BranchInstance, number, void>;
  addBranch: BelongsToManyAddOne<BranchInstance, number, void>;
  createBranch: BelongsToManyCreateOne<BranchAttributes, BranchInstance, void>;
  removeBranch: BelongsToManyRemoveOne<BranchInstance, number>;
  removeBranches: BelongsToManyRemoveMany<BranchInstance, number>;
  hasBranch: BelongsToManyHasOne<BranchInstance, number>;
  hasBranches: BelongsToManyHasMany<BranchInstance, number>;
  countBranches: BelongsToManyCount;
};

//
//  DataTypes
// ~~~~~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/unit/sql/data-types.test.js
//

Sequelize.STRING;
Sequelize.STRING( 1234 );
Sequelize.STRING( { length : 1234 } );
Sequelize.STRING( 1234 ).BINARY;
Sequelize.STRING.BINARY;
Sequelize.TEXT;
Sequelize.TEXT( 'tiny' );
Sequelize.TEXT( { length : 'tiny' } );
Sequelize.TEXT( 'medium' );
Sequelize.TEXT( 'long' );
Sequelize.CHAR;
Sequelize.CHAR( 12 );
Sequelize.CHAR( { length : 12 } );
Sequelize.CHAR( 12 ).BINARY;
Sequelize.CHAR.BINARY;
Sequelize.BOOLEAN;
Sequelize.DATE;
Sequelize.DATE(6);
Sequelize.UUID;
Sequelize.UUIDV1;
Sequelize.UUIDV4;
Sequelize.NOW;
Sequelize.INTEGER;
Sequelize.INTEGER.UNSIGNED;
Sequelize.INTEGER.UNSIGNED.ZEROFILL;
Sequelize.INTEGER( 11 );
Sequelize.INTEGER( { length : 11 } );
Sequelize.INTEGER( 11 ).UNSIGNED;
Sequelize.INTEGER( 11 ).UNSIGNED.ZEROFILL;
Sequelize.INTEGER( 11 ).ZEROFILL;
Sequelize.INTEGER( 11 ).ZEROFILL.UNSIGNED;
Sequelize.BIGINT;
Sequelize.BIGINT.UNSIGNED;
Sequelize.BIGINT.UNSIGNED.ZEROFILL;
Sequelize.BIGINT( 11 );
Sequelize.BIGINT( { length : 11 } );
Sequelize.BIGINT( 11 ).UNSIGNED;
Sequelize.BIGINT( 11 ).UNSIGNED.ZEROFILL;
Sequelize.BIGINT( 11 ).ZEROFILL;
Sequelize.BIGINT( 11 ).ZEROFILL.UNSIGNED;
Sequelize.REAL.UNSIGNED;
Sequelize.REAL( 11 );
Sequelize.REAL( { length : 11 } );
Sequelize.REAL( 11 ).UNSIGNED;
Sequelize.REAL( 11 ).UNSIGNED.ZEROFILL;
Sequelize.REAL( 11 ).ZEROFILL;
Sequelize.REAL( 11 ).ZEROFILL.UNSIGNED;
Sequelize.REAL( 11, 12 );
Sequelize.REAL( 11, 12 ).UNSIGNED;
Sequelize.REAL( { length : 11, decimals : 12 } ).UNSIGNED;
Sequelize.REAL( 11, 12 ).UNSIGNED.ZEROFILL;
Sequelize.REAL( 11, 12 ).ZEROFILL;
Sequelize.REAL( 11, 12 ).ZEROFILL.UNSIGNED;
Sequelize.DOUBLE;
Sequelize.DOUBLE.UNSIGNED;
Sequelize.DOUBLE( 11 );
Sequelize.DOUBLE( 11 ).UNSIGNED;
Sequelize.DOUBLE( { length : 11 } ).UNSIGNED;
Sequelize.DOUBLE( 11 ).UNSIGNED.ZEROFILL;
Sequelize.DOUBLE( 11 ).ZEROFILL;
Sequelize.DOUBLE( 11 ).ZEROFILL.UNSIGNED;
Sequelize.DOUBLE( 11, 12 );
Sequelize.DOUBLE( 11, 12 ).UNSIGNED;
Sequelize.DOUBLE( 11, 12 ).UNSIGNED.ZEROFILL;
Sequelize.DOUBLE( 11, 12 ).ZEROFILL;
Sequelize.DOUBLE( 11, 12 ).ZEROFILL.UNSIGNED;
Sequelize.FLOAT;
Sequelize.FLOAT.UNSIGNED;
Sequelize.FLOAT( 11 );
Sequelize.FLOAT( 11 ).UNSIGNED;
Sequelize.FLOAT( 11 ).UNSIGNED.ZEROFILL;
Sequelize.FLOAT( 11 ).ZEROFILL;
Sequelize.FLOAT( { length : 11 } ).ZEROFILL;
Sequelize.FLOAT( 11 ).ZEROFILL.UNSIGNED;
Sequelize.FLOAT( 11, 12 );
Sequelize.FLOAT( 11, 12 ).UNSIGNED;
Sequelize.FLOAT( { length : 11, decimals : 12 } ).UNSIGNED;
Sequelize.FLOAT( 11, 12 ).UNSIGNED.ZEROFILL;
Sequelize.FLOAT( 11, 12 ).ZEROFILL;
Sequelize.FLOAT( 11, 12 ).ZEROFILL.UNSIGNED;
Sequelize.NUMERIC;
Sequelize.NUMERIC( 15, 5 );
Sequelize.DECIMAL;
Sequelize.DECIMAL( 10, 2 );
Sequelize.DECIMAL( { precision : 10, scale : 2 } );
Sequelize.DECIMAL( 10 );
Sequelize.DECIMAL( { precision : 10 } );
Sequelize.ENUM( 'value 1', 'value 2' );
Sequelize.BLOB;
Sequelize.BLOB( 'tiny' );
Sequelize.BLOB( 'medium' );
Sequelize.BLOB( { length : 'medium' } );
Sequelize.BLOB( 'long' );
Sequelize.ARRAY( Sequelize.STRING );
Sequelize.ARRAY( Sequelize.STRING( 100 ) );
Sequelize.ARRAY( Sequelize.INTEGER );
Sequelize.ARRAY( Sequelize.HSTORE );
Sequelize.ARRAY( Sequelize.ARRAY( Sequelize.STRING ) );
Sequelize.ARRAY( Sequelize.TEXT );
Sequelize.ARRAY( Sequelize.DATE );
Sequelize.ARRAY( Sequelize.BOOLEAN );
Sequelize.ARRAY( Sequelize.DECIMAL );
Sequelize.ARRAY( Sequelize.DECIMAL( 6 ) );
Sequelize.ARRAY( Sequelize.DECIMAL( 6, 4 ) );
Sequelize.ARRAY( Sequelize.DOUBLE );
Sequelize.ARRAY( Sequelize.REAL );
Sequelize.ARRAY( Sequelize.JSON );
Sequelize.ARRAY( Sequelize.JSONB );
Sequelize.GEOMETRY;
Sequelize.GEOMETRY( 'POINT' );
Sequelize.GEOMETRY( 'LINESTRING' );
Sequelize.GEOMETRY( 'POLYGON' );
Sequelize.GEOMETRY( 'POINT', 4326 );
Sequelize.VIRTUAL;
Sequelize.VIRTUAL( Sequelize.STRING );
Sequelize.VIRTUAL( Sequelize.DATE , ['property1', 'property2']);
new Sequelize.VIRTUAL( Sequelize.STRING );
new Sequelize.VIRTUAL( Sequelize.DATE , ['property1', 'property2']);

//
//  Deferrable
// ~~~~~~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/sequelize/deferrable.test.js
//

Sequelize.Deferrable.NOT;
Sequelize.Deferrable.INITIALLY_IMMEDIATE;
Sequelize.Deferrable.INITIALLY_DEFERRED;
Sequelize.Deferrable.SET_DEFERRED;
Sequelize.Deferrable.SET_DEFERRED( ['taskTableName_user_id_fkey'] );
Sequelize.Deferrable.SET_IMMEDIATE;
Sequelize.Deferrable.SET_IMMEDIATE( ['taskTableName_user_id_fkey'] );

//
//  Errors
// ~~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/error.test.js
//

Sequelize.Error;
Sequelize.ValidationError;
s.Error;
s.ValidationError;
new s.ValidationError( 'Validation Error', [
  new s.ValidationErrorItem( '<field name> cannot be null', 'notNull Violation', '<field name>', 'value' )
  , new s.ValidationErrorItem( '<field name> cannot be an array or an object', 'string violation',
    '<field name>', 'value' )
] );
new s.Error();
new s.ValidationError('message');
new s.ValidationErrorItem( 'invalid', 'type', 'first_name', 'value' );
new s.ValidationErrorItem( 'invalid', 'type', 'last_name', 'value' );
new s.DatabaseError( new Error( 'original database error message' ) );
new s.ConnectionError( new Error( 'original connection error message' ) );
new s.ConnectionRefusedError( new Error( 'original connection error message' ) );
new s.AccessDeniedError( new Error( 'original connection error message' ) );
new s.HostNotFoundError( new Error( 'original connection error message' ) );
new s.HostNotReachableError( new Error( 'original connection error message' ) );
new s.InvalidConnectionError( new Error( 'original connection error message' ) );
new s.ConnectionTimedOutError( new Error( 'original connection error message' ) );
new s.EmptyResultError();

const uniqueConstraintError: Sequelize.ValidationError = new s.UniqueConstraintError({});

//
//  Hooks
// ~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/hooks.test.js
//

User.addHook( 'afterCreate', function( instance : Model<any>, options : Object, next : Function ) { next(); } );
User.addHook( 'afterCreate', 'myHook', function( instance : Model<any>, options : Object, next : Function) { next(); } );
s.addHook( 'beforeInit', function( config : Object, options : Object ) { } );
User.hook( 'afterCreate', 'myHook', function( instance : Model<any>, options : Object, next : Function) { next(); } );
User.hook( 'afterCreate', 'myHook', function( instance : Model<any>, options : Object, next : Function ) { next(); } );

User.removeHook( 'afterCreate', 'myHook' );

User.hasHook( 'afterCreate' );
User.hasHooks( 'afterCreate' );

User.beforeValidate( function( user, options ) { user.isNewRecord; } );
User.beforeValidate( 'myHook', function( user, options ) { user.isNewRecord; } );

User.afterValidate( function( user, options ) { user.isNewRecord; } );
User.afterValidate( 'myHook', function( user, options ) { user.isNewRecord; } );

User.beforeCreate( function( user, options ) { user.isNewRecord; } );
User.beforeCreate( function( user, options, fn ) {fn();} );
User.beforeCreate( 'myHook', function( user, options ) { user.isNewRecord; } );

User.afterCreate( function( user, options ) { user.isNewRecord; } );
User.afterCreate( function( user, options, fn ) {fn();} );
User.afterCreate( 'myHook', function( user, options ) { user.isNewRecord; } );

User.beforeDestroy( function( user, options ) {throw new Error( 'Whoops!' );} );
User.beforeDestroy( function( user, options, fn ) {fn();} );
User.beforeDestroy( 'myHook', function( user, options ) {throw new Error( 'Whoops!' );} );
User.beforeDelete( function( user, options ) {throw new Error( 'Whoops!' );} );
User.beforeDelete( 'myHook', function( user, options ) {throw new Error( 'Whoops!' );} );

User.afterDestroy( function( user, options ) {throw new Error( 'Whoops!' );} );
User.afterDestroy( 'myHook', function( user, options ) {throw new Error( 'Whoops!' );} );
User.afterDestroy( function( user, options, fn ) {fn();} );
User.afterDelete( function( user, options ) {throw new Error( 'Whoops!' );} );
User.afterDelete( 'myHook', function( user, options ) {throw new Error( 'Whoops!' );} );

User.beforeUpdate( function( user, options ) {throw new Error( 'Whoops!' ); } );
User.beforeUpdate( 'myHook', function( user, options ) {throw new Error( 'Whoops!' ); } );

User.afterUpdate( function( user, options ) {throw new Error( 'Whoops!' );} );
User.afterUpdate( 'myHook', function( user, options ) {throw new Error( 'Whoops!' );} );

User.beforeUpsert( function( user, options ) { throw new Error('Whoops'); } );
User.beforeUpsert( 'myHook', function( user, options ) { throw new Error('Whoops'); } );

User.afterUpsert( function( result, options ) { throw new Error('Whoops'); } );
User.afterUpsert( 'myHook', function( result, options ) { throw new Error('Whoops'); } );

User.beforeBulkCreate( function( daos, options ) { throw new Error( 'Whoops!' );} );
User.beforeBulkCreate( 'myHook', function( daos, options ) { throw new Error( 'Whoops!' );} );
User.beforeBulkCreate( function( daos, options, fn ) {fn();} );

User.afterBulkCreate( function( daos, options ) {throw new Error( 'Whoops!' ); } );
User.afterBulkCreate( 'myHook', function( daos, options ) {throw new Error( 'Whoops!' ); } );
User.afterBulkCreate( function( daos, options, fn ) {fn();} );

User.beforeBulkDestroy( function( options ) {throw new Error( 'Whoops!' );} );
User.beforeBulkDestroy( function( options, fn ) {fn();} );
User.beforeBulkDestroy( 'myHook', function( options, fn ) {fn();} );
User.beforeBulkDelete( 'myHook', function( options, fn ) {fn();} );

User.afterBulkDestroy( function( options ) {throw new Error( 'Whoops!' );} );
User.afterBulkDestroy( function( options, fn ) {fn();} );
User.afterBulkDestroy( 'myHook', function( options, fn ) {fn();} );
User.afterBulkDelete( 'myHook', function( options, fn ) {fn();} );

User.beforeBulkUpdate( function( options ) {throw new Error( 'Whoops!' );} );
User.beforeBulkUpdate( 'myHook', function( options ) {throw new Error( 'Whoops!' );} );

User.afterBulkUpdate( function( options ) {throw new Error( 'Whoops!' );} );
User.afterBulkUpdate( 'myHook', function( options ) {throw new Error( 'Whoops!' );} );

User.beforeFind( function( options ) {} );
User.beforeFind( 'myHook', function( options ) {} );

User.beforeFindAfterExpandIncludeAll( function( options ) {} );
User.beforeFindAfterExpandIncludeAll( 'myHook', function( options ) {} );

User.beforeFindAfterOptions( function( options ) {} );
User.beforeFindAfterOptions( 'myHook', function( options ) {} );

User.afterFind( function( user ) {} );
User.afterFind( 'myHook', function( user ) {} );

User.beforeSync( function( options ) {} );
User.beforeSync( 'myHook', function( options ) {} );

User.afterSync( function( options ) {} );
User.afterSync( 'myHook', function( options ) {} );

s.beforeDefine( function( attributes, options ) {} );
s.beforeDefine( 'myHook', function( attributes, options ) {} );

s.afterDefine( function( model ) {} );
s.afterDefine( 'myHook', function( model ) {} );

s.beforeInit( function( config, options ) {} );
s.beforeInit( 'myHook', function( attributes, options ) {} );

s.afterInit( function( model ) {} );
s.afterInit( 'myHook', function( model ) {} );

s.beforeBulkSync( function( options ) {} );
s.beforeBulkSync( 'myHook', function( options ) {} );

s.afterBulkSync( function( options ) {} );
s.afterBulkSync( 'myHook', function( options ) {} );

s.define( 'User', {}, {
  hooks : {
    beforeValidate : function( user, options, fn ) {fn();},
    afterValidate : function( user, options, fn ) {fn();},
    beforeCreate : function( user, options, fn ) {fn();},
    afterCreate : function( user, options, fn ) {fn();},
    beforeDestroy : function( user, options, fn ) {fn();},
    afterDestroy : function( user, options, fn ) {fn();},
    beforeDelete : function( user, options, fn ) {fn();},
    afterDelete : function( user, options, fn ) {fn();},
    beforeUpdate : function( user, options, fn ) {fn();},
    afterUpdate : function( user, options, fn ) {fn();}
  }
} );

//
//  Instance
// ~~~~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/instance.test.js
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/instance/update.test.js
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/instance/values.test.js
//

user.isNewRecord = true;

user.Model.build( { a : 'b' } );

user.sequelize.close();

user.where();

user.getDataValue( '' );

user.setDataValue( '', '' );
user.setDataValue( '', {} );

user.get( 'aNumber', { plain : true, clone : true } );
user.get();

user.set( 'email', 'B' );
user.set( { name : 'B', bio : 'B' } ).save().then( ( p ) => p );
user.set( 'birthdate', new Date() );
user.set( { id : 1, t : 'c', q : [{ id : 1, n : 'a' }, { id : 2, n : 'Beta' }], u : { id : 1, f : 'b', l : 'd' } } );
user.setAttributes( { a : 3 } );
user.setAttributes( { id : 1, a : 'n', c : [{ id : 1 }, { id : 2, f : 'e' }], x : { id : 1, f : 'h', l : 'd' } } );

user.changed( 'name' );
user.changed();

user.previous( 'name' );

user.save().then( ( p ) => p );
user.save( { fields : ['a'] } ).then( ( p ) => p );
user.save( { transaction : t } );

user.reload();
user.reload( { attributes : ['bNumber'] } );
user.reload( { transaction : t } );

user.validate();

user.update( { bNumber : 2 }, { where : { id : 1 } } );
user.update( { username : 'userman' }, { silent : true } );
user.update( { username : 'yolo' }, { logging : function() { } } );
user.update( { username : 'bar' }, { where : { username : 'foo' }, transaction : t } ).then( ( p ) => p );
user.updateAttributes( { a : 3 } ).then( ( p ) => p );
user.updateAttributes( { a : 3 }, { fields : ['secretValue'], logging : function(  ) {} } );

user.destroy().then( ( p ) => p );
user.destroy( { logging : function(  ) {} } );
user.destroy( { transaction : t } ).then( ( p ) => p );

user.restore();

user.increment( 'number', { by : 2 } ).then( ( p ) => p );
user.increment( ['aNumber'], { by : 2, where : { bNumber : 1 } } ).then( ( p ) => p );
user.increment( ['aNumber'], { by : 2 } ).then( ( p ) => p );
user.increment( 'aNumber' ).then( ( p ) => p );
user.increment( { 'aNumber' : 1, 'bNumber' : 2 } ).then( ( p ) => p );
user.increment( 'number', { by : 2, transaction : t } ).then( ( p ) => p );

user.decrement( 'aNumber', { by : 2 } ).then( ( p ) => p );
user.decrement( ['aNumber'], { by : 2 } ).then( ( p ) => p );
user.decrement( 'aNumber' ).then( ( p ) => p );
user.decrement( { 'aNumber' : 1, 'bNumber' : 2 } ).then( ( p ) => p );
user.decrement( 'number', { by : 2, transaction : t } ).then( ( p ) => p );

user.equals( user );

user.equalsOneOf( [user, user] );

user.toJSON();


//  Model
// ~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/model.test.js
//

User.removeAttribute( 'id' );

User.sync( { force : true } ).then( function() { } );
User.sync( { force : true, logging : function() { } } );

User.drop();

User.schema( 'special' );
User.schema( 'special' ).create( { age : 3 }, { logging : function(  ) {} } );

User.getTableName();

User.addScope('lowAccess', { where : { parent_id : 2 } });
User.addScope('lowAccess', { where : { parent_id : 2 } }, { override: true });
User.addScope('lowAccessWithParam', function(id: number) {
  return { where : { parent_id : id } }
} );

User.scope( 'lowAccess' ).count();
User.scope( [ 'lowAccess', { method: ['lowAccessWithParam', 2] } ] )

User.findAll();
User.findAll( { where : { data : { employment : null } } } );
User.findAll( { where : { aNumber : { gte : 10 } } } ).then( ( u ) => u[0].isNewRecord );
User.findAll( { where : [s.or( { u : 'b' }, { u : ';' } ), s.and( { id : [1, 2] } )], include : [{ model : User }] } );
User.findAll( {
  where : [s.or( { a : 'b' }, { c : 'd' } ), s.and( { id : [1, 2, 3] },
    s.or( { deletedAt : null }, { deletedAt : { gt : new Date( 0 ) } } ) )]
} );
User.findAll( { paranoid : false, where : [' IS NOT NULL '], include : [{ model : User }] } );
User.findAll( { include : [{ model : Task, paranoid: false }] } );
User.findAll( { transaction : t } );
User.findAll( { where : { data : { name : { last : 's' }, employment : { $ne : 'a' } } }, order : [['id', 'ASC']] } );
User.findAll( { where : { username : ['boo', 'boo2'] } } );
User.findAll( { where : { username : { like : '%2' } } } );
User.findAll( { where : { theDate : { '..' : ['2013-01-02', '2013-01-11'] } } } );
User.findAll( { where : { intVal : { '!..' : [8, 10] } } } );
User.findAll( { where : { theDate : { between : ['2013-01-02', '2013-01-11'] } } } );
User.findAll( { where : { theDate : { between : ['2013-01-02', '2013-01-11'] }, intVal : 10 } } );
User.findAll( { where : { theDate : { between : ['2012-12-10', '2013-01-02'] } } } );
User.findAll( { where : { theDate : { nbetween : ['2013-01-04', '2013-01-20'] } } } );
User.findAll( { order : [s.col( 'name' )] } );
User.findAll( { order : [['theDate', 'DESC']] } );
User.findAll( { include : [User], order : [[User, User, 'numYears', 'c']] } );
User.findAll( { include : [{ model : User, include : [User, { model : User, as : 'residents' }] }] } );
User.findAll( { order : [[User, { model : User, as : 'residents' }, 'lastName', 'c']] } );
User.findAll( { include : [User], order : [[User, 'name', 'c']] } );
User.findAll( { include : [{ all : 'HasMany', attributes : ['name'] }] } );
User.findAll( { include : [{ all : true }, { model : User, attributes : ['id'] }] } );
User.findAll( { include : [{ all : 'BelongsTo' }] } );
User.findAll( { include : [{ all : true }] } );
User.findAll( { where : { username : 'barfooz' }, raw : true } );
User.findAll( { where : { name : 'worker' }, include : [{ model : User, as : 'ToDos' }] } );
User.findAll( { where : { user_id : 1 }, attributes : ['a', 'b'], include : [{ model : User, attributes : ['c'] }] } );
User.findAll( { order : s.literal( 'email =' ) } );
User.findAll( { order : [s.literal( 'email = ' + s.escape( 'test@sequelizejs.com' ) )] } );
User.findAll( { order : [['id', ';DELETE YOLO INJECTIONS']] } );
User.findAll( { include : [User], order : [[User, 'id', ';DELETE YOLO INJECTIONS']] } );
User.findAll( { include : [User], order : [['id', 'ASC NULLS LAST'], [User, 'id', 'DESC NULLS FIRST']] } );
User.findAll( { include : [{ model : User, where : { title : 'DoDat' }, include : [{ model : User }] }] } );
User.findAll( { attributes: ['username', 'data']});
User.findAll( { attributes: {include: ['username', 'data']} });
User.findAll( { attributes: [['username', 'user_name'], ['email', 'user_email']] });
User.findAll( { attributes: [s.fn('count', Sequelize.col('*'))] });
// currently suffering from flow union type bugs
// User.findAll( { attributes: [[s.fn('count', Sequelize.col('*')), 'count']] });
// User.findAll( { attributes: [[s.fn('count', Sequelize.col('*')), 'count']], group: ['sex'] });
// User.findAll( { attributes: [s.cast(s.fn('count', Sequelize.col('*')), 'INTEGER')] });
// User.findAll( { attributes: [[s.cast(s.fn('count', Sequelize.col('*')), 'INTEGER'), 'count']] });
User.findAll( { where : s.fn('count', [0, 10]) } );
User.findAll( { where: s.where(s.fn('lower', s.col('email')), s.fn('lower', 'TEST@SEQUELIZEJS.COM')) } );
User.findAll( { subQuery: false, include : [User], order : [[User, User, 'numYears', 'c']] } );
User.findAll( { rejectOnEmpty: true });


User.findById( 'a string' );
User.findByPk( 'a string' );

User.findOne( { where : { username : 'foo' } } );
User.findOne( { where : { id : 1 }, attributes : ['id', ['username', 'name']] } );
User.findOne( { where : { id : 1 }, attributes : ['id'] } );
User.findOne( { where : { username : 'foo' }, logging : function(  ) { } } );
User.findOne( { limit : 10 } );
User.findOne( { where : { title : 'homework' }, include : [User] } );
User.findOne( { where : { name : 'environment' }, include : [{ model : User, as : 'PrivateDomain' }] } );
User.findOne( { where : { username : 'foo' }, transaction : t } ).then( ( p ) => p );
User.findOne( { include : [User] } );
User.findOne( { include : [{ model : User, as : 'Work' }] } );
User.findOne( { where : { name : 'worker' }, include : [{ model : User, as : 'ToDo' }] } );
User.findOne( { include : [{ model : User, as : 'ToDo' }, { model : User, as : 'DoTo' }] } );
User.findOne( { where : { name : 'worker' }, include : [User] } );
User.findOne( { where : { name : 'Boris' }, include : [User, { model : User, as : 'Photos' }] } );
User.findOne( { where : { username : 'someone' }, include : [User] } );
User.findOne( { where : { username : 'barfooz' }, raw : true } );
User.findOne( { where : s.fn('count', []) } );
User.findOne( { where: s.where(s.fn('lower', s.col('email')), s.fn('lower', 'TEST@SEQUELIZEJS.COM')) } );
/* NOTE https://github.com/DefinitelyTyped/DefinitelyTyped/pull/5590
User.findOne( { updatedAt : { ne : null } } );
 */
User.find( { where : { intVal : { gt : 5 } } } );
User.find( { where : { intVal : { lte : 5 } } } );

User.count();
User.count( { transaction : t } );
User.count().then( function( c ) { c.toFixed(); } );
User.count( { where : ["username LIKE '%us%'"] } );
User.count( { include : [{ model : User, required : false }] } );
User.count( { distinct : true, include : [{ model : User, required : false }] } );
User.count( { attributes : ['data'], group : ['data'] } );
User.count( { where : { access_level : { gt : 5 } } } );

User.findAndCountAll( { offset : 5, limit : 1, include : [User, { model : User, as : 'a' }] } );

User.max( 'age', { transaction : t } );
User.max( 'age' );
User.max( 'age', { logging : function(  ) { } } );

User.min( 'age', { transaction : t } );
User.min( 'age' );
User.min( 'age', { logging : function(  ) { } } );

User.sum( 'order' );
User.sum( 'age', { where : { 'gender' : 'male' } } );
User.sum( 'age', { logging : function(  ) { } } );

User.build( { username : 'John Wayne' } ).save();
User.build();
User.build( { id : 1, T : [{ n : 'a' }, { id : 2 }], A : { id : 1, n : 'a', c : 'a' } }, { include : [User, Task] } );
User.build( { id : 1, }, { include : [{ model : User, as : 'followers' }, { model : Task, as : 'categories' }] } );

User.create();
User.create( { createdAt : 1, updatedAt : 2 }, { silent : true } );
User.create( {}, { returning : true } );
User.create( { intVal : s.literal( 'CAST(1-2 AS' ) } );
User.create( { secretValue : s.fn( 'upper', 'sequelize' ) } );
User.create( { myvals : [1, 2, 3, 4], mystr : ['One', 'Two', 'Three', 'Four'] } );
User.create( { name : 'Fluffy Bunny', smth : 'else' }, { logging : function(  ) {} } );
User.create( {}, { fields : [] } );
User.create( { name : 'Yolo Bear', email : 'yolo@bear.com' }, { fields : ['name'] } );
User.create( { title : 'Chair', User : { first_name : 'Mick', last_name : 'Broadstone' } }, { include : [User] } );
User.create( { title : 'Chair', creator : { first_name : 'Matt', last_name : 'Hansen' } }, { include : [User] } );
User.create( { id : 1, title : 'e', Tags : [{ id : 1, name : 'c' }, { id : 2, name : 'd' }] }, { include : [User] } );
User.create( { id : 'My own ID!' } ).then( ( i ) => i.isNewRecord );

let findOrRetVal: Promise<[Model<Object>, boolean]>;
findOrRetVal = User.findOrInitialize( { where : { username : 'foo' } } );
findOrRetVal = User.findOrInitialize( { where : { username : 'foo' }, transaction : t } );
findOrRetVal = User.findOrInitialize( { where : { username : 'foo' }, defaults : { foo : 'asd' }, transaction : t } );
findOrRetVal = User.findOrInitialize( { where : { username : 'foo' }, defaults : { foo : 'asd' }, paranoid : false } );

findOrRetVal = User.findOrCreate( { where : { a : 'b' }, defaults : { json : { a : { b : 'c' }, d : [1, 2, 3] } } } );
findOrRetVal = User.findOrCreate( { where : { a : 'b' }, defaults : { json : 'a', data : 'b' } } );
/* NOTE https://github.com/DefinitelyTyped/DefinitelyTyped/pull/5590
User.findOrCreate( { where : { a : 'b' }, transaction : t, lock : t.LOCK.UPDATE } );
 */
findOrRetVal = User.findOrCreate( { where : { a : 'b' }, logging : function(  ) { } } );
findOrRetVal = User.findOrCreate( { where : { username : 'Username' }, defaults : { data : 'some data' }, transaction : t } );
findOrRetVal = User.findOrCreate( { where : { objectId : 'asdasdasd' }, defaults : { username : 'gottlieb' } } );
findOrRetVal = User.findOrCreate( { where : { id : null }, defaults : { name : Math.random().toString() } } );
findOrRetVal = User.findOrCreate( { where : { email : 'unique.email.@d.com', companyId : Math.floor( Math.random() * 5 ) } } );
findOrRetVal = User.findOrCreate( { where : { objectId : 1 }, defaults : { bool : false } } );

User.upsert( { id : 42, username : 'doe', foo : s.fn( 'upper', 'mixedCase2' ) } );

User.bulkCreate( [{ aNumber : 10 }, { aNumber : 12 }] ).then( ( i ) => i[0].isNewRecord );
User.bulkCreate( [{ username : 'bar' }, { username : 'bar' }, { username : 'bar' }] );
User.bulkCreate( [{}, {}], { validate : true, individualHooks : true } );
User.bulkCreate( [{ style : 'ipa' }], { logging : function() { } } );
User.bulkCreate( [{ a : 'b', c : 'd', e : 'f' }, { a : 'b', c : 'd', e : 'f' }], { fields : ['a', 'b'] } );
User.bulkCreate( [{ name : 'foo', code : '123' }, { code : 'c' }, { name : 'bar', code : '1' }], { validate : true } );
User.bulkCreate( [{ name : 'foo', code : '123' }, { code : '1234' }], { fields : ['code'], validate : true } );
User.bulkCreate( [{ name : 'a', c : 'b' }, { name : 'e', c : 'f' }], { fields : ['e', 'f'], ignoreDuplicates : true } );

User.truncate();
User.truncate( { cascade : true } );
User.truncate( { force : true } );
User.truncate( { cascade: true, force : true } );

User.destroy( { where : { client_id : 13 } } ).then( ( a ) => a.toFixed() );
User.destroy( { force : true } );
User.destroy( { where : {}, transaction : t } );
User.destroy( { where : { access_level : { lt : 5 } } } );
User.destroy( { truncate : true } );
User.destroy( { where : {} } );

User.restore( { where : { secretValue : '42' } } );

User.update( { username : 'ruben' }, { where : {} } );
User.update( { username : 'ruben' }, { where : { access_level : { lt : 5 } } } );
User.update( { username : 'ruben' }, { where : { username : 'dan' } } );
User.update( { username : 'bar' }, { where : { username : 'foo' }, transaction : t } );
User.update( { username : 'Bill', secretValue : '43' }, { where : { secretValue : '42' }, fields : ['username'] } );
User.update( { username : s.cast( '1', 'char' ) }, { where : { username : 'John' } } );
User.update( { username : s.fn( 'upper', s.col( 'username' ) ) }, { where : { username : 'John' } } );
User.update( { username : 'Bill' }, { where : { secretValue : '42' }, returning : true } );
User.update( { secretValue : '43' }, { where : { username : 'Peter' }, limit : 1 } );
User.update( { name : Math.random().toString() }, { where : { id : '1' } } );
User.update( { a : { b : 10, c : 'd' } }, { where : { username : 'Jan' }, sideEffects : false } );
User.update( { geometry : { type : 'Point', coordinates : [49.807222, -86.984722] } }, {
  where : {
    u : {
      u : 'u',
      geometry : { type : 'Point', coordinates : [49.807222, -86.984722] }
    }
  }
} );
User.update( {
  geometry : {
    type : 'Polygon',
    coordinates : [[[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], [100.0, 1.0], [100.0, 0.0]]]
  }
}, {
  where : {
    username : {
      username : 'username',
      geometry : { type : 'Point', coordinates : [49.807222, -86.984722] }
    }
  }
} );

User.unscoped().find( { where : { username : 'bob' } } );
User.unscoped().count();

//
//  Model Statics
// ~~~~~~~~~~~~~~~
//

User.sequelize.query;
User.QueryInterface.addColumn;
User.QueryGenerator.selectQuery;
user.sequelize.query;

//
//  Query Interface
// ~~~~~~~~~~~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/query-interface.test.js
//

let queryInterface = s.getQueryInterface();

queryInterface.dropAllTables();
queryInterface.showAllTables( { logging : function() { } } );
queryInterface.createTable( 'table', { name : Sequelize.STRING }, { logging : function() { } } );
queryInterface.createTable( 'skipme', { name : Sequelize.STRING } );
queryInterface.dropAllTables( { skip : ['skipme'] } );
queryInterface.dropTable( 'Group', { logging : function() { } } );
queryInterface.addIndex( 'Group', { fields: ['username', 'isAdmin'] } );
queryInterface.showIndex( 'Group', { logging : function() { } } );
queryInterface.removeIndex( 'Group', ['username', 'isAdmin'], { logging : function() { } } );
queryInterface.showIndex( 'Group' );
queryInterface.createTable( 'table', { name : { type : Sequelize.STRING } }, { schema : 'schema' } );
queryInterface.showIndex( { schema : 'schema', tableName : 'table' }, { logging : function() {} } );
queryInterface.describeTable( '_Users', { logging : function() {} } );
queryInterface.createTable( 's', { table_id : { type : Sequelize.INTEGER, primaryKey : true, autoIncrement : true } } );
queryInterface.createTable( 'SomeTable', { someEnum : Sequelize.ENUM( 'value1', 'value2', 'value3' ) } );
queryInterface.createTable( 'SomeTable', { someEnum : { type : Sequelize.ENUM, values : ['b1', 'b2', 'b3'] } } );
queryInterface.createTable( 't', { someEnum : { type : Sequelize.ENUM, values : ['c1', 'c2', 'c3'], field : 'd' } } );
queryInterface.createTable( 'User', { name : { type : Sequelize.STRING } }, { schema : 'hero' } );
queryInterface.rawSelect( 'User', { schema : 'hero', logging : function() {} }, 'name' );
queryInterface.renameColumn( '_Users', 'username', 'pseudo', { logging : function() {} } );
queryInterface.renameColumn( { schema : 'archive', tableName : 'Users' }, 'username', 'pseudo' );
queryInterface.renameColumn( '_Users', 'username', 'pseudo' );
queryInterface.createTable( { tableName : 'y', schema : 'a' },
  { id : { type : Sequelize.INTEGER, primaryKey : true, autoIncrement : true }, currency : Sequelize.INTEGER } );
queryInterface.changeColumn( { tableName : 'a', schema : 'b' }, 'c', { type : Sequelize.FLOAT },
  { logging : () => s } );
queryInterface.createTable( 'users', { id : { type : Sequelize.INTEGER, primaryKey : true, autoIncrement : true } } );
queryInterface.createTable( 'level', { id : { type : Sequelize.INTEGER, primaryKey : true, autoIncrement : true } } );
queryInterface.addColumn( 'users', 'someEnum', Sequelize.ENUM( 'value1', 'value2', 'value3' ) );
queryInterface.addColumn( 'users', 'so', { type : Sequelize.ENUM, values : ['value1', 'value2', 'value3'] } );
queryInterface.createTable( 'hosts', {
  id : {
    type : Sequelize.INTEGER,
    primaryKey : true,
    autoIncrement : true
  },
  admin : {
    type : Sequelize.INTEGER,
    references : {
      model : User,
      key : 'id'
    }
  },
  operator : {
    type : Sequelize.INTEGER,
    references : {
      model : User,
      key : 'id'
    },
    onUpdate : 'cascade'
  },
  owner : {
    type : Sequelize.INTEGER,
    references : {
      model : User,
      key : 'id'
    },
    onUpdate : 'cascade',
    onDelete : 'set null'
  }
} );
//
//
//  Query Types
// ~~~~~~~~~~~~~
//

s.getDialect();
s.validate();
s.authenticate();
s.isDefined( '' );
s.model( 'pp' );
s.query( '', { raw : true } );
s.query( '' );
s.query( '' ).then( function( res ) {} );
s.query( { query : 'select ? as foo, ? as bar', values : [1, 2] }, { raw : true, replacements : [1, 2] } );
s.query( '', { raw : true, nest : false } );
s.query( 'select ? as foo, ? as bar', { type : sequelize.QueryTypes.SELECT, replacements : [1, 2] } );
s.query( { query : 'select ? as foo, ? as bar', values : [1, 2] }, { type : s.QueryTypes.SELECT } );
s.query( 'select :one as foo, :two as bar', { raw : true, replacements : { one : 1, two : 2 } } );
s.transaction().then( function( t ) { s.set( { foo : 'bar' }, { transaction : t } ); } );
s.define( 'foo', { bar : Sequelize.STRING }, { collate : 'utf8_bin' } );
s.define( 'Foto', { name : Sequelize.STRING }, { tableName : 'photos' } );
s.databaseVersion().then( function( version ) { } );

//
//  Sequelize
// ~~~~~~~~~~~
//

new Sequelize( 'db', 'user', 'pw', { logging : false } );
new Sequelize( 'db', 'user', 'pass', {
  dialect : '',
  port : 99999,
  pool : {}
} );
new Sequelize( '' ).query( '', { type : s.QueryTypes.FOREIGNKEYS, logging : function() {} } );
new Sequelize( 'sqlite://test.sqlite' );
new Sequelize( 'wat', 'trololo', 'wow', { port : 99999 } );
new Sequelize( 'localhost', 'wtf', 'lol', { port : 99999 } );
new Sequelize( 'sequelize', null, null, {
  replication : {
    read : {
      host : 'localhost',
      username : 'omg',
      password : 'lol'
    }
  }
} );
new Sequelize( {
  database: 'db',
  username: 'user',
  password: 'pass',
  retry: {
    match: ['failed'],
    max: 3
  },
  typeValidation: true
} );
new Sequelize( {
  database: 'db',
  username: 'user',
  password: 'pass',
  retry: {
    match: [/failed/],
    max: 3
  },
  typeValidation: true
} );

new Sequelize({
  operatorsAliases: false,
});

new Sequelize({
  operatorsAliases: {
    $and: Sequelize.Op.and,
    customAlias: Sequelize.Op.or,
  },
});

s.model( 'Project' );
s.models['Project'];
s.define( 'Project', {
  name : Sequelize.STRING
} );

let testModel = s.define( 'User', {
  username : Sequelize.STRING,
  secretValue : Sequelize.STRING,
  data : Sequelize.STRING,
  intVal : Sequelize.INTEGER,
  theDate : Sequelize.DATE,
  aBool : Sequelize.BOOLEAN
} );
testModel = s.define( 'FrozenUser', {}, { freezeTableName : true } );
s.define( 'UserWithClassAndInstanceMethods', {}, {
  classMethods : { doSmth : function() { return 1; } },
  instanceMethods : { makeItSo : function() { return 2; } }
} );
s.define( 'UserCol', {
  id : {
    type : Sequelize.STRING,
    defaultValue : 'User',
    primaryKey : true
  }
} );
s.define( 'UserWithTwoAutoIncrements', {
  userid : { type : Sequelize.INTEGER, primaryKey : true, autoIncrement : true },
  userscore : { type : Sequelize.INTEGER, primaryKey : true, autoIncrement : true }
} );
s.define( 'Foo', {
  field : Sequelize.INTEGER
}, {
  validate : {
    field : function() {}
  }
} );
let UserTable = s.define( 'UserCol', {
  aNumber : Sequelize.INTEGER,
  createdAt : {
    type : Sequelize.DATE,
    defaultValue : new Date()
  },
  updatedAt : {
    type : Sequelize.DATE,
    defaultValue : new Date()
  }
}, { timestamps : true } );

s.define( 'UserCol', {
  aNumber : Sequelize.INTEGER
}, {
  timestamps : true,
  updatedAt : 'updatedOn',
  createdAt : 'dateCreated',
  deletedAt : 'deletedAtThisTime',
  paranoid : true
} );
s.define( 'UpdatingUser', {
  name : Sequelize.STRING
}, {
  timestamps : true,
  updatedAt : false,
  createdAt : false,
  deletedAt : 'deletedAtThisTime',
  paranoid : true
} );
s.define( 'TaskBuild', {
  title : {
    type : Sequelize.STRING( 50 ),
    allowNull : false,
    defaultValue : ''
  }
}, {
  setterMethods : {
    title : function() { }
  }
} );
s.define( 'UserCol', {
  aNumber : Sequelize.INTEGER
}, {
  paranoid : true,
  underscored : true
} );

s.define( 'UserWithUniqueUsername', {
  username : { type : Sequelize.STRING, unique : { name : 'user_and_email', msg : 'User and email must be unique' } },
  email : { type : Sequelize.STRING, unique : 'user_and_email' }
} );

s.define( 'UserWithUniqueUsername', {
  user_id : { type : Sequelize.INTEGER },
  email : { type : Sequelize.STRING }
}, {
  indexes : [
    {
      name : 'user_and_email_index',
      unique : true,
      method : 'BTREE',
      fields : ['user_id', { attribute : 'email', collate : 'en_US', order : 'DESC', length : 5 }],
      where : {
        user_id : { $not: null }
      }
    },
    {
      fields: ['data'],
      using: 'gin',
      operator: 'jsonb_path_ops'
    }
  ]
} );

s.define( 'TaskBuild', {
  title : { type : Sequelize.STRING, defaultValue : 'a task!' },
  foo : { type : Sequelize.INTEGER, defaultValue : 2 },
  bar : { type : Sequelize.DATE },
  foobar : { type : Sequelize.TEXT, defaultValue : 'asd' },
  flag : { type : Sequelize.BOOLEAN, defaultValue : false }
} );
s.define( 'ProductWithSettersAndGetters1', {
  price : {
    type : Sequelize.INTEGER,
    get : function() {
      return 'answer = ' + this.getDataValue( 'price' );
    },
    set : function( v: number ) {
      return this.setDataValue( 'price', v + 42 );
    }
  }
} );
s.define( 'ProductWithSettersAndGetters2', {
  priceInCents : Sequelize.INTEGER
}, {
  setterMethods : {
    price : function( value ) {
      this.dataValues.priceInCents = value * 100;
    }
  },
  getterMethods : {
    price : function() {
      return '$' + (this.getDataValue( 'priceInCents' ) / 100);
    },

    priceInCents : function() {
      return this.dataValues.priceInCents;
    }
  }
} );

s.define( 'post', {
  title : Sequelize.STRING,
  authorId : { type : Sequelize.INTEGER, references : testModel, referencesKey : 'id' }
} );
s.define( 'post', {
  title : Sequelize.STRING,
  authorId : { type : Sequelize.INTEGER, references : { model : testModel, key : 'id' } }
} );

s.define( 'User', {
  username : Sequelize.STRING,
  geometry : Sequelize.GEOMETRY( 'POINT' )
} );

s.define( 'ScopeMe', {
  username : Sequelize.STRING,
  email : Sequelize.STRING,
  access_level : Sequelize.INTEGER,
  other_value : Sequelize.INTEGER,
  parent_id : Sequelize.INTEGER
}, {
  defaultScope : {
    where : {
      access_level : {
        gte : 5
      }
    }
  },
  scopes : {
    isTony : {
      where : {
        username : 'tony'
      }
    },
  }
} );
s.define( 'company', {
  active : Sequelize.BOOLEAN
}, {
  defaultScope : {
    where : { active : true }
  },
  scopes : {
    notActive : {
      where : {
        active : false
      }
    },
    reversed : {
      order : [['id', 'DESC']]
    }
  }
} );
s.define( 'profile', {
  active : Sequelize.BOOLEAN
}, {
  defaultScope : {
    where : { active : true }
  },
  scopes : {
    notActive : {
      where : {
        active : false
      }
    },
  }
} );

s.define( 'ScopeMe', {
  username : Sequelize.STRING,
  email : Sequelize.STRING,
  access_level : Sequelize.INTEGER,
  other_value : Sequelize.INTEGER
}, {
  defaultScope : {
    where : {
      access_level : {
        gte : 5
      }
    }
  },
  scopes : {
    lowAccess : {
      where : {
        access_level : {
          lte : 5
        }
      }
    },
    withOrder : {
      order : 'username'
    }
  }
} );

// Generic find options
interface ChairAttributes {
  id: number;
  color: string;
  legs: number;
}
declare class ChairInstance extends Model<ChairAttributes> {}

const Chair = s.define('chair', {});

Chair.findAll({
  where: {
    color: 'blue',
    legs: { $in: [3, 4] },
  },
});

// https://github.com/facebook/flow/issues/3258 :(
// Chair.findAll({
//   where: {
//     color: 'blue',
//     legs: { [Sequelize.Op.in]: [3, 4] },
//   },
// });

// If you want to use a property that isn't explicitly on the model's Attributes
// use the find-function's generic type parameter.
Chair.findAll({
  where: {
    customProperty: 123,
  }
});
Chair.findAll({
  where: {
    customProperty1: 123,
    customProperty2: 456,
  }
});

s.define( 'ScopeMe', {
  username : Sequelize.STRING,
  email : Sequelize.STRING,
  access_level : Sequelize.INTEGER,
  other_value : Sequelize.INTEGER
}, {
  defaultScope : {
    where : {
      access_level : {
        gte : 5
      }
    }
  },
  scopes : {
    lowAccess : {
      where : {
        access_level : {
          lte : 5
        }
      }
    }
  }
} );

s.define( 'user', {
  id : {
    type : Sequelize.INTEGER,
    allowNull : false,
    primaryKey : true,
    autoIncrement : true,
    field : 'userId'
  },
  name : {
    type : Sequelize.STRING,
    field : 'full_name'
  },
  taskCount : {
    type : Sequelize.INTEGER,
    field : 'task_count',
    defaultValue : 0,
    allowNull : false
  }
}, {
  tableName : 'users',
  timestamps : false
} );
s.define( 'task', {
  id : {
    type : Sequelize.INTEGER,
    allowNull : false,
    primaryKey : true,
    autoIncrement : true,
    field : 'taskId'
  },
  title : {
    type : Sequelize.STRING,
    field : 'name'
  }
}, {
  tableName : 'tasks',
  timestamps : false
} );
s.define( 'comment', {
  id : {
    type : Sequelize.INTEGER,
    allowNull : false,
    primaryKey : true,
    autoIncrement : true,
    field : 'commentId'
  },
  text : {
    type : Sequelize.STRING,
    field : 'comment_text'
  },
  notes : {
    type : Sequelize.STRING,
    field : 'notes'
  }
}, {
  tableName : 'comments',
  timestamps : false
} );
s.define( 'test', {
  id : {
    type : Sequelize.INTEGER,
    field : 'test_id',
    autoIncrement : true,
    primaryKey : true,
    validate : {
      min : 1
    }
  },
  title : {
    allowNull : false,
    type : Sequelize.STRING( 255 ),
    field : 'test_title'
  }
}, {
  timestamps : true,
  underscored : true,
  freezeTableName : true
} );
s.define( 'testBooeanVersionOption', {
  version : {
    type : Sequelize.INTEGER,
  }
}, {
  version: true
} );
s.define( 'testStringVersionOption', {
  nameOfOptimisticLockColumn : {
    type : Sequelize.INTEGER,
  }
}, {
  version: "nameOfOptimisticLockColumn"
} );
s.define( 'User', {
  deletedAt : {
    type : Sequelize.DATE,
    field : 'deleted_at'
  }
}, {
  timestamps : true,
  paranoid : true
} );

s.define( 'TriggerTest', {
  id : {
    type : Sequelize.INTEGER,
    field : 'test_id',
    autoIncrement : true,
    primaryKey : true,
    validate : {
      min : 1
    }
  },
  title : {
    allowNull : false,
    type : Sequelize.STRING( 255 ),
    field : 'test_title'
  }
}, {
  timestamps : false,
  underscored : true,
  hasTrigger : true
} );

s.define('DefineOptionsIndexesTest', {
  id: {
    type: Sequelize.INTEGER,
    autoIncrement: true,
    primaryKey: true,
    validate: {
      min: 1
    }
  },
  email: {
    allowNull: false,
    type: Sequelize.STRING(255),
    set: function (val) {
      if (typeof val === "string") {
        val = val.toLowerCase();
      } else {
        throw new Error("email must be a string");
      }
      this.setDataValue("email", val);
    }
  }
}, {
  timestamps: false,
  indexes: [
    {
      name: "DefineOptionsIndexesTest_lower_email",
      unique: true,
      fields: [
        Sequelize.fn("LOWER", Sequelize.col("email"))
      ]
    }
  ]
} );

//
//  Transaction
// ~~~~~~~~~~~~~
//
//  https://github.com/sequelize/sequelize/blob/v3.4.1/test/integration/transaction.test.js
//

s.transaction().then( function( t ) {

  t.commit();
  t.rollback();

  User.find( {
    where : {
      username : 'John'
    },
    include : [User],
    lock : t.LOCK.UPDATE,
    transaction : t
  } );
  User.find( {
    where : {
      username : 'John'
    },
    include : [User],
    lock : {
      level : t.LOCK.UPDATE,
      of : User
    },
    transaction : t
  } );
  User.update( {
    active : true
  }, {
    where : {
      active : false
    },
    transaction : t
  } );
  User.find( {
    where : {
      username : 'jan'
    },
    lock : t.LOCK.NO_KEY_UPDATE,
    transaction : t
  } );
  User.find( {
    where : {
      username : 'jan'
    },
    lock : t.LOCK.KEY_SHARE,
    transaction : t
  } );

} );

s.transaction({
  isolationLevel: s.Transaction.ISOLATION_LEVELS.READ_COMMITTED
}).then(function(t2) {
  return User.find({
    where: {
      username: 'jan'
    },
    lock: t2.LOCK.UPDATE,
    transaction: t2
  });
});

let autoCallback = (t: Transaction):Promise<string> => { return Promise.resolve('a') }
let callbackValidator = (r:string) => {}
s.transaction( autoCallback ).then( callbackValidator );
s.transaction( { isolationLevel : 'SERIALIZABLE' }, autoCallback ).then( callbackValidator );
s.transaction( { isolationLevel : s.Transaction.ISOLATION_LEVELS.SERIALIZABLE }, autoCallback ).then( callbackValidator );
s.transaction( { isolationLevel : s.Transaction.ISOLATION_LEVELS.READ_COMMITTED }, autoCallback ).then( callbackValidator );

// transaction types
new Sequelize( '', { transactionType: 'DEFERRED' } );
new Sequelize( '', { transactionType: Sequelize.Transaction.TYPES.DEFERRED} );
new Sequelize( '', { transactionType: Sequelize.Transaction.TYPES.IMMEDIATE} );
new Sequelize( '', { transactionType: Sequelize.Transaction.TYPES.EXCLUSIVE} );
s.transaction( { type : 'DEFERRED' }, (t) => Promise.resolve() );
s.transaction( { type : s.Transaction.TYPES.DEFERRED }, (t) => Promise.resolve() );
s.transaction( { type : s.Transaction.TYPES.IMMEDIATE }, (t) => Promise.resolve() );
s.transaction( { type : s.Transaction.TYPES.EXCLUSIVE }, (t) => Promise.resolve() );

// promise transaction
let asyncAutoCallback = async (t: Transaction):Promise<string> => 'a'
s.transaction( asyncAutoCallback ).then( callbackValidator );

// afterCommit hook
s.transaction(async (t: Transaction): Promise<void> => {
  t.afterCommit(async (t: Transaction): Promise<void> => {
  });
});

// sync options types
s.sync({
  alter: true,
  force: true,
  hooks: false,
  searchPath: 'some/path/',
  schema: 'schema',
  logging: () => {},
  match: new RegExp('\d{4,}')
});
