不要怂,就是干,撸起袖子干!

Commit 287607a0 by papb

fix(types): better support for readonly arrays

1 parent 914279aa
......@@ -40,8 +40,8 @@ export interface ModelHooks<M extends Model = Model, TAttributes = any> {
instance: M,
options: InstanceUpdateOptions<TAttributes> | CreateOptions<TAttributes>
): HookReturn;
beforeBulkCreate(instances: M[], options: BulkCreateOptions<TAttributes>): HookReturn;
afterBulkCreate(instances: M[], options: BulkCreateOptions<TAttributes>): HookReturn;
beforeBulkCreate(instances: readonly M[], options: BulkCreateOptions<TAttributes>): HookReturn;
afterBulkCreate(instances: readonly M[], options: BulkCreateOptions<TAttributes>): HookReturn;
beforeBulkDestroy(options: DestroyOptions<TAttributes>): HookReturn;
afterBulkDestroy(options: DestroyOptions<TAttributes>): HookReturn;
beforeBulkRestore(options: RestoreOptions<TAttributes>): HookReturn;
......@@ -52,7 +52,7 @@ export interface ModelHooks<M extends Model = Model, TAttributes = any> {
beforeCount(options: CountOptions<TAttributes>): HookReturn;
beforeFindAfterExpandIncludeAll(options: FindOptions<TAttributes>): HookReturn;
beforeFindAfterOptions(options: FindOptions<TAttributes>): HookReturn;
afterFind(instancesOrInstance: M[] | M | null, options: FindOptions<TAttributes>): HookReturn;
afterFind(instancesOrInstance: readonly M[] | M | null, options: FindOptions<TAttributes>): HookReturn;
beforeSync(options: SyncOptions): HookReturn;
afterSync(options: SyncOptions): HookReturn;
beforeBulkSync(options: SyncOptions): HookReturn;
......
import {Model, SaveOptions, Sequelize, FindOptions} from "sequelize"
import { expectTypeOf } from "expect-type";
import { Model, SaveOptions, Sequelize, FindOptions } from "sequelize";
import { ModelHooks } from "../lib/hooks";
/*
* covers types/lib/sequelize.d.ts
*/
Sequelize.beforeSave((t: TestModel, options: SaveOptions) => {});
Sequelize.afterSave((t: TestModel, options: SaveOptions) => {});
Sequelize.afterFind((t: TestModel[] | TestModel | null, options: FindOptions) => {});
Sequelize.afterFind('namedAfterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {});
class TestModel extends Model {}
/*
* covers types/lib/hooks.d.ts
*/
export const sequelize = new Sequelize('uri', {
hooks: {
beforeSave (m: Model, options: SaveOptions) {},
afterSave (m: Model, options: SaveOptions) {},
afterFind (m: Model[] | Model | null, options: FindOptions) {},
}
});
class TestModel extends Model {
}
const hooks: Partial<ModelHooks> = {
beforeSave(t: TestModel, options: SaveOptions) { },
afterSave(t: TestModel, options: SaveOptions) { },
afterFind(t: TestModel | TestModel[] | null, options: FindOptions) { },
beforeSave(m, options) {
expectTypeOf(m).toEqualTypeOf<TestModel>();
expectTypeOf(options).toMatchTypeOf<SaveOptions>(); // TODO consider `.toEqualTypeOf` instead ?
},
afterSave(m, options) {
expectTypeOf(m).toEqualTypeOf<TestModel>();
expectTypeOf(options).toMatchTypeOf<SaveOptions>(); // TODO consider `.toEqualTypeOf` instead ?
},
afterFind(m, options) {
expectTypeOf(m).toEqualTypeOf<readonly TestModel[] | TestModel | null>();
expectTypeOf(options).toEqualTypeOf<FindOptions>();
}
};
TestModel.init({}, {sequelize, hooks })
export const sequelize = new Sequelize('uri', { hooks });
TestModel.init({}, { sequelize, hooks });
TestModel.addHook('beforeSave', (t: TestModel, options: SaveOptions) => { });
TestModel.addHook('afterSave', (t: TestModel, options: SaveOptions) => { });
TestModel.addHook('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { });
TestModel.addHook('beforeSave', hooks.beforeSave!);
TestModel.addHook('afterSave', hooks.afterSave!);
TestModel.addHook('afterFind', hooks.afterFind!);
/*
* covers types/lib/model.d.ts
*/
TestModel.beforeSave((t: TestModel, options: SaveOptions) => { });
TestModel.afterSave((t: TestModel, options: SaveOptions) => { });
TestModel.afterFind((t: TestModel | TestModel[] | null, options: FindOptions) => { });
TestModel.beforeSave(hooks.beforeSave!);
TestModel.afterSave(hooks.afterSave!);
TestModel.afterFind(hooks.afterFind!);
/*
* covers types/lib/sequelize.d.ts
*/
Sequelize.beforeSave(hooks.beforeSave!);
Sequelize.afterSave(hooks.afterSave!);
Sequelize.afterFind(hooks.afterFind!);
Sequelize.afterFind('namedAfterFind', hooks.afterFind!);
import { expectTypeOf } from "expect-type";
import { AndOperator, fn, Model, Op, OrOperator, Sequelize, WhereOperators, WhereOptions, literal, where as whereFn } from 'sequelize';
import Transaction from '../lib/transaction';
......@@ -5,15 +6,9 @@ class MyModel extends Model {
public hi!: number;
}
let where: WhereOptions;
// Simple options
// From https://sequelize.org/master/en/v4/docs/querying/
/**
* Literal values
* @see WhereValue
*/
where = {
expectTypeOf({
string: 'foo',
strings: ['foo'],
number: 1,
......@@ -23,34 +18,32 @@ where = {
buffers: [Buffer.alloc(0)],
null: null,
date: new Date()
};
}).toMatchTypeOf<WhereOptions>();
// Optional values
let whereWithOptionals: { needed: number; optional?: number } = { needed: 2 };
where = whereWithOptionals;
expectTypeOf<{ needed: number; optional?: number }>().toMatchTypeOf<WhereOptions>();
// Misusing optional values (typings allow this, sequelize will throw an error during runtime)
// This might be solved by updates to typescript itself (https://github.com/microsoft/TypeScript/issues/13195)
whereWithOptionals = { needed: 2, optional: undefined };
where = whereWithOptionals;
// expectTypeOf({ needed: 2, optional: undefined }).not.toMatchTypeOf<WhereOptions>();
// Operators
const and: AndOperator = {
expectTypeOf({
[Op.and]: { a: 5 }, // AND (a = 5)
};
const typedAnd: AndOperator<{ a: number }> = {
}).toMatchTypeOf<AndOperator>();
expectTypeOf({
[Op.and]: { a: 5 }, // AND (a = 5)
};
}).toMatchTypeOf<AndOperator<{ a: number }>>();
const or: OrOperator = {
expectTypeOf({
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
};
const typedOr: OrOperator<{ a: number }> = {
}).toMatchTypeOf<OrOperator>();
expectTypeOf({
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
};
}).toMatchTypeOf<OrOperator<{ a: number }>>();
let operators: WhereOperators = {
expectTypeOf({
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
......@@ -76,74 +69,44 @@ let operators: WhereOperators = {
[Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only)
[Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only)
};
operators = {
[Op.like]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike
};
// Combinations
MyModel.findOne({ where: or });
MyModel.findOne({ where: and });
where = Sequelize.and();
where = Sequelize.or();
where = { [Op.and]: [] };
where = {
rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }),
};
where = {
rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }),
};
where = {
rank: {
[Op.or]: {
[Op.lt]: 1000,
[Op.eq]: null,
},
},
};
// rank < 1000 OR rank IS NULL
where = {
} as const).toMatchTypeOf<WhereOperators>();
expectTypeOf({
[Op.like]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat']
[Op.iLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat']
[Op.notLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat']
[Op.notILike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat']
}).toMatchTypeOf<WhereOperators>();
// Complex where options via combinations
expectTypeOf([
{ [Op.or]: [{ a: 5 }, { a: 6 }] },
Sequelize.and(),
Sequelize.or(),
{ [Op.and]: [] },
{ rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }) },
{ rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }) },
{ rank: { [Op.or]: { [Op.lt]: 1000, [Op.eq]: null } } },
{
createdAt: {
[Op.lt]: new Date(),
[Op.gt]: new Date(Date.now() - 24 * 60 * 60 * 1000),
}
},
};
// createdAt < [timestamp] AND createdAt > [timestamp]
where = {
[Op.or]: [
{
title: {
[Op.like]: 'Boat%',
},
[Op.or]: [
{ title: { [Op.like]: 'Boat%' } },
{ description: { [Op.like]: '%boat%' } }
]
},
{
description: {
[Op.like]: '%boat%',
},
},
],
};
// title LIKE 'Boat%' OR description LIKE '%boat%'
// Containment
where = {
meta: {
[Op.contains]: {
site: {
url: 'http://google.com',
},
},
url: 'https://sequelize.org/'
}
}
},
meta2: {
[Op.contains]: ['stringValue1', 'stringValue2', 'stringValue3']
......@@ -151,7 +114,58 @@ where = {
meta3: {
[Op.contains]: [1, 2, 3, 4]
},
};
},
{
name: 'a project',
[Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }]
},
{
id: {
[Op.or]: [[1, 2, 3], { [Op.gt]: 10 }]
},
name: 'a project'
},
{
id: {
[Op.or]: [[1, 2, 3], { [Op.gt]: 10 }]
},
name: 'a project'
},
{
name: 'a project',
type: {
[Op.and]: [['a', 'b'], { [Op.notLike]: '%z' }],
},
},
{
name: 'a project',
[Op.not]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }],
},
{
meta: {
video: {
url: {
[Op.ne]: null,
},
},
},
},
{
'meta.audio.length': {
[Op.gt]: 20,
},
},
{
[Op.and]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }],
},
{
[Op.gt]: fn('NOW'),
},
whereFn('test', { [Op.gt]: new Date() }),
literal('true'),
fn('LOWER', 'asd'),
{ [Op.lt]: Sequelize.literal('SOME_STRING') }
]).toMatchTypeOf<WhereOptions[]>();
// Relations / Associations
// Find all projects with a least one task where task.state === project.task
......@@ -164,7 +178,9 @@ MyModel.findAll({
],
});
MyModel.findOne({
{
const where: WhereOptions = 0 as any;
MyModel.findOne({
include: [
{
include: [{ model: MyModel, where }],
......@@ -173,9 +189,13 @@ MyModel.findOne({
},
],
where,
});
MyModel.destroy({ where });
MyModel.update({ hi: 1 }, { where });
});
MyModel.destroy({ where });
MyModel.update({ hi: 1 }, { where });
// Where as having option
MyModel.findAll({ having: where });
}
// From https://sequelize.org/master/en/v4/docs/models-usage/
......@@ -232,86 +252,6 @@ MyModel.findAll({
},
});
// Complex filtering / NOT queries
where = {
name: 'a project',
[Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }],
};
where = {
id: {
[Op.or]: [[1, 2, 3], { [Op.gt]: 10 }],
},
name: 'a project',
};
where = {
name: 'a project',
type: {
[Op.and]: [['a', 'b'], { [Op.notLike]: '%z' }],
},
};
// [Op.not] example:
where = {
name: 'a project',
[Op.not]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }],
};
// JSONB
// Nested object
where = {
meta: {
video: {
url: {
[Op.ne]: null,
},
},
},
};
// Nested key
where = {
'meta.audio.length': {
[Op.gt]: 20,
},
};
// Operator symbols
where = {
[Op.and]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }],
};
// Fn as value
where = {
[Op.gt]: fn('NOW'),
};
where = whereFn('test', {
[Op.gt]: new Date(),
});
// Literal as where
where = literal('true')
where = fn('LOWER', 'asd')
MyModel.findAll({
where: literal('true')
})
// Where as having option
MyModel.findAll({
having: where
});
where = {
[Op.lt]: Sequelize.literal('SOME_STRING')
}
Sequelize.where(
Sequelize.cast(Sequelize.col('SOME_COL'), 'INTEGER'), {
[Op.lt]: Sequelize.literal('LIT'),
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!