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

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