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

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;
......
......@@ -70,7 +70,7 @@ export interface Paranoid {
paranoid?: boolean;
}
export type GroupOption = string | Fn | Col | (string | Fn | Col)[];
export type GroupOption = string | Fn | Col | readonly (string | Fn | Col)[];
/**
* Options to pass to Model on drop
......@@ -103,7 +103,7 @@ export interface ScopeOptions {
* any arguments, or an array, where the first element is the name of the method, and consecutive elements
* are arguments to that method. Pass null to remove all scopes, including the default.
*/
method: string | [string, ...unknown[]];
method: string | readonly [string, ...unknown[]];
}
/**
......@@ -123,15 +123,15 @@ export type WhereOptions<TAttributes = any> =
* _PG only_
*/
export interface AnyOperator {
[Op.any]: (string | number)[];
[Op.any]: readonly (string | number)[];
}
/** Undocumented? */
export interface AllOperator {
[Op.all]: (string | number | Date | Literal)[];
[Op.all]: readonly (string | number | Date | Literal)[];
}
export type Rangable = [number, number] | [Date, Date] | Literal;
export type Rangable = readonly [number, number] | readonly [Date, Date] | Literal;
/**
* Operators that can be used in WhereOptions
......@@ -144,7 +144,7 @@ export interface WhereOperators {
*
* _PG only_
*/
[Op.any]?: (string | number | Literal)[] | Literal;
[Op.any]?: readonly (string | number | Literal)[] | Literal;
/** Example: `[Op.gte]: 6,` becomes `>= 6` */
[Op.gte]?: number | string | Date | Literal;
......@@ -165,10 +165,10 @@ export interface WhereOperators {
[Op.between]?: Rangable;
/** Example: `[Op.in]: [1, 2],` becomes `IN [1, 2]` */
[Op.in]?: (string | number | Literal)[] | Literal;
[Op.in]?: readonly (string | number | Literal)[] | Literal;
/** Example: `[Op.notIn]: [1, 2],` becomes `NOT IN [1, 2]` */
[Op.notIn]?: (string | number | Literal)[] | Literal;
[Op.notIn]?: readonly (string | number | Literal)[] | Literal;
/**
* Examples:
......@@ -205,14 +205,14 @@ export interface WhereOperators {
*
* Example: `[Op.contains]: [1, 2]` becomes `@> [1, 2]`
*/
[Op.contains]?: (string | number)[] | Rangable;
[Op.contains]?: readonly (string | number)[] | Rangable;
/**
* PG array contained by operator
*
* Example: `[Op.contained]: [1, 2]` becomes `<@ [1, 2]`
*/
[Op.contained]?: (string | number)[] | Rangable;
[Op.contained]?: readonly (string | number)[] | Rangable;
/** Example: `[Op.gt]: 6,` becomes `> 6` */
[Op.gt]?: number | string | Date | Literal;
......@@ -311,12 +311,12 @@ export interface WhereOperators {
/** Example: `[Op.or]: [{a: 5}, {a: 6}]` becomes `(a = 5 OR a = 6)` */
export interface OrOperator<TAttributes = any> {
[Op.or]: WhereOptions<TAttributes> | WhereOptions<TAttributes>[] | WhereValue<TAttributes> | WhereValue<TAttributes>[];
[Op.or]: WhereOptions<TAttributes> | readonly WhereOptions<TAttributes>[] | WhereValue<TAttributes> | readonly WhereValue<TAttributes>[];
}
/** Example: `[Op.and]: {a: 5}` becomes `AND (a = 5)` */
export interface AndOperator<TAttributes = any> {
[Op.and]: WhereOptions<TAttributes> | WhereOptions<TAttributes>[] | WhereValue<TAttributes> | WhereValue<TAttributes>[];
[Op.and]: WhereOptions<TAttributes> | readonly WhereOptions<TAttributes>[] | WhereValue<TAttributes> | readonly WhereValue<TAttributes>[];
}
/**
......@@ -324,7 +324,7 @@ export interface AndOperator<TAttributes = any> {
*/
export interface WhereGeometryOptions {
type: string;
coordinates: (number[] | number)[];
coordinates: readonly (number[] | number)[];
}
/**
......@@ -345,7 +345,7 @@ export type WhereValue<TAttributes = any> =
| OrOperator<TAttributes>
| AndOperator<TAttributes>
| WhereGeometryOptions
| (string | number | Buffer | WhereAttributeHash<TAttributes>)[]; // implicit [Op.or]
| readonly (string | number | Buffer | WhereAttributeHash<TAttributes>)[]; // implicit [Op.or]
/**
* A hash of attributes to describe your search.
......@@ -444,7 +444,7 @@ export interface IncludeOptions extends Filterable<any>, Projectable, Paranoid {
/**
* Load further nested related models
*/
include?: Includeable[];
include?: readonly Includeable[];
/**
* Order include. Only available when setting `separate` to true.
......@@ -464,44 +464,44 @@ export type OrderItem =
| Fn
| Col
| Literal
| [OrderItemColumn, string]
| [OrderItemAssociation, OrderItemColumn]
| [OrderItemAssociation, OrderItemColumn, string]
| [OrderItemAssociation, OrderItemAssociation, OrderItemColumn]
| [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string]
| [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn]
| [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string]
| [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn]
| [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string]
export type Order = string | Fn | Col | Literal | OrderItem[];
| readonly [OrderItemColumn, string]
| readonly [OrderItemAssociation, OrderItemColumn]
| readonly [OrderItemAssociation, OrderItemColumn, string]
| readonly [OrderItemAssociation, OrderItemAssociation, OrderItemColumn]
| readonly [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string]
| readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn]
| readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string]
| readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn]
| readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string]
export type Order = string | Fn | Col | Literal | readonly OrderItem[];
/**
* Please note if this is used the aliased property will not be available on the model instance
* as a property but only via `instance.get('alias')`.
*/
export type ProjectionAlias = [string | Literal | Fn, string];
export type ProjectionAlias = readonly [string | Literal | Fn, string];
export type FindAttributeOptions =
| (string | ProjectionAlias)[]
| readonly (string | ProjectionAlias)[]
| {
exclude: string[];
include?: (string | ProjectionAlias)[];
exclude: readonly string[];
include?: readonly (string | ProjectionAlias)[];
}
| {
exclude?: string[];
include: (string | ProjectionAlias)[];
exclude?: readonly string[];
include: readonly (string | ProjectionAlias)[];
};
export interface IndexHint {
type: IndexHints;
values: string[];
values: readonly string[];
}
export interface IndexHintable {
/**
* MySQL only.
*/
indexHints?: IndexHint[];
indexHints?: readonly IndexHint[];
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
......@@ -521,7 +521,7 @@ export interface FindOptions<TAttributes = any>
* If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in
* the as attribute when eager loading Y).
*/
include?: Includeable | Includeable[];
include?: Includeable | readonly Includeable[];
/**
* Specifies an ordering. If a string is provided, it will be escaped. Using an array, you can provide
......@@ -592,7 +592,7 @@ export interface CountOptions<TAttributes = any>
/**
* Include options. See `find` for details
*/
include?: Includeable | Includeable[];
include?: Includeable | readonly Includeable[];
/**
* Apply COUNT(DISTINCT(col))
......@@ -645,7 +645,7 @@ export interface BuildOptions {
*
* TODO: See set
*/
include?: Includeable | Includeable[];
include?: Includeable | readonly Includeable[];
}
export interface Silent {
......@@ -664,7 +664,7 @@ export interface CreateOptions<TAttributes = any> extends BuildOptions, Logging,
/**
* If set, only columns matching those in fields will be saved
*/
fields?: (keyof TAttributes)[];
fields?: readonly (keyof TAttributes)[];
/**
* On Duplicate
......@@ -699,7 +699,7 @@ export interface FindOrCreateOptions<TAttributes = any, TCreationAttributes = TA
/**
* The fields to insert / update. Defaults to all fields
*/
fields?: (keyof TAttributes)[];
fields?: readonly (keyof TAttributes)[];
/**
* Default values to use if building a new instance
*/
......@@ -713,7 +713,7 @@ export interface UpsertOptions<TAttributes = any> extends Logging, Transactionab
/**
* The fields to insert / update. Defaults to all fields
*/
fields?: (keyof TAttributes)[];
fields?: readonly (keyof TAttributes)[];
/**
* Return the affected rows (only for postgres)
......@@ -733,7 +733,7 @@ export interface BulkCreateOptions<TAttributes = any> extends Logging, Transacti
/**
* Fields to insert (defaults to all fields)
*/
fields?: (keyof TAttributes)[];
fields?: readonly (keyof TAttributes)[];
/**
* Should each row be subject to validation before it is inserted. The whole insert will fail if one row
......@@ -758,17 +758,17 @@ export interface BulkCreateOptions<TAttributes = any> extends Logging, Transacti
* Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL,
* MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated.
*/
updateOnDuplicate?: (keyof TAttributes)[];
updateOnDuplicate?: readonly (keyof TAttributes)[];
/**
* Include options. See `find` for details
*/
include?: Includeable | Includeable[];
include?: Includeable | readonly Includeable[];
/**
* Return all columns or only the specified columns for the affected rows (only for postgres)
*/
returning?: boolean | (keyof TAttributes)[];
returning?: boolean | readonly (keyof TAttributes)[];
}
/**
......@@ -846,7 +846,7 @@ export interface UpdateOptions<TAttributes = any> extends Logging, Transactionab
/**
* Fields to update (defaults to all fields)
*/
fields?: (keyof TAttributes)[];
fields?: readonly (keyof TAttributes)[];
/**
* Should each row be subject to validation before it is inserted. The whole insert will fail if one row
......@@ -969,7 +969,7 @@ export interface SaveOptions<TAttributes = any> extends Logging, Transactionable
* An optional array of strings, representing database columns. If fields is provided, only those columns
* will be validated and saved.
*/
fields?: (keyof TAttributes)[];
fields?: readonly (keyof TAttributes)[];
/**
* If false, validations won't be run.
......@@ -990,15 +990,15 @@ export interface SaveOptions<TAttributes = any> extends Logging, Transactionable
*/
export interface ModelValidateOptions {
/**
* is: ["^[a-z]+$",'i'] // will only allow letters
* is: /^[a-z]+[Op./i] // same as the previous example using real RegExp
* - `{ is: ['^[a-z]+$','i'] }` will only allow letters
* - `{ is: /^[a-z]+$/i }` also only allows letters
*/
is?: string | (string | RegExp)[] | RegExp | { msg: string; args: string | (string | RegExp)[] | RegExp };
is?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp };
/**
* not: ["[a-z]",'i'] // will not allow letters
* - `{ not: ['[a-z]','i'] }` will not allow letters
*/
not?: string | (string | RegExp)[] | RegExp | { msg: string; args: string | (string | RegExp)[] | RegExp };
not?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp };
/**
* checks for email format (foo@bar.com)
......@@ -1093,22 +1093,22 @@ export interface ModelValidateOptions {
/**
* check the value is not one of these
*/
notIn?: string[][] | { msg: string; args: string[][] };
notIn?: ReadonlyArray<readonly string[]> | { msg: string; args: ReadonlyArray<readonly string[]> };
/**
* check the value is one of these
*/
isIn?: string[][] | { msg: string; args: string[][] };
isIn?: ReadonlyArray<readonly string[]> | { msg: string; args: ReadonlyArray<readonly string[]> };
/**
* don't allow specific substrings
*/
notContains?: string[] | string | { msg: string; args: string[] | string };
notContains?: readonly string[] | string | { msg: string; args: readonly string[] | string };
/**
* only allow values with length between 2 and 10
*/
len?: [number, number] | { msg: string; args: [number, number] };
len?: readonly [number, number] | { msg: string; args: readonly [number, number] };
/**
* only allow uuids
......@@ -1133,12 +1133,12 @@ export interface ModelValidateOptions {
/**
* only allow values
*/
max?: number | { msg: string; args: [number] };
max?: number | { msg: string; args: readonly [number] };
/**
* only allow values >= 23
*/
min?: number | { msg: string; args: [number] };
min?: number | { msg: string; args: readonly [number] };
/**
* only allow arrays
......@@ -1150,20 +1150,10 @@ export interface ModelValidateOptions {
*/
isCreditCard?: boolean | { msg: string; args: boolean };
// TODO: Enforce 'rest' indexes to have type `(value: unknown) => boolean`
// Blocked by: https://github.com/microsoft/TypeScript/issues/7765
/**
* custom validations are also possible
*
* Implementation notes :
*
* We can't enforce any other method to be a function, so :
*
* ```typescript
* [name: string] : ( value : unknown ) => boolean;
* ```
*
* doesn't work in combination with the properties above
*
* @see https://github.com/Microsoft/TypeScript/issues/1889
* Custom validations are also possible
*/
[name: string]: unknown;
}
......@@ -1209,7 +1199,7 @@ export interface ModelScopeOptions<TAttributes = any> {
/**
* Name of the scope and it's query
*/
[scopeName: string]: FindOptions<TAttributes> | ((...args: any[]) => FindOptions<TAttributes>);
[scopeName: string]: FindOptions<TAttributes> | ((...args: readonly any[]) => FindOptions<TAttributes>);
}
/**
......@@ -1334,7 +1324,7 @@ export interface ModelAttributeColumnOptions<M extends Model = Model> extends Co
* }, { sequelize })
* ```
*/
values?: string[];
values?: readonly string[];
/**
* Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying
......@@ -1426,7 +1416,7 @@ export interface ModelOptions<M extends Model = Model> {
/**
* Indexes for the provided database table
*/
indexes?: ModelIndexesOptions[];
indexes?: readonly ModelIndexesOptions[];
/**
* Override the name of the createdAt column if a string is provided, or disable it if false. Timestamps
......@@ -1564,7 +1554,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
/**
* The name of the primary key attributes
*/
public static readonly primaryKeyAttributes: string[];
public static readonly primaryKeyAttributes: readonly string[];
/**
* An object hash from alias to association object
......@@ -1737,7 +1727,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
*/
public static scope<M extends Model>(
this: ModelStatic<M>,
options?: string | ScopeOptions | (string | ScopeOptions)[] | WhereAttributeHash<M>
options?: string | ScopeOptions | readonly (string | ScopeOptions)[] | WhereAttributeHash<M>
): ModelCtor<M>;
/**
......@@ -1757,7 +1747,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
public static addScope<M extends Model>(
this: ModelStatic<M>,
name: string,
scope: (...args: any[]) => FindOptions<M['_attributes']>,
scope: (...args: readonly any[]) => FindOptions<M['_attributes']>,
options?: AddScopeOptions
): void;
......@@ -1969,7 +1959,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
*/
public static bulkBuild<M extends Model>(
this: ModelStatic<M>,
records: (M['_creationAttributes'])[],
records: ReadonlyArray<M['_creationAttributes']>,
options?: BuildOptions
): M[];
......@@ -2059,7 +2049,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
*/
public static bulkCreate<M extends Model>(
this: ModelStatic<M>,
records: (M['_creationAttributes'])[],
records: ReadonlyArray<M['_creationAttributes']>,
options?: BulkCreateOptions<M['_attributes']>
): Promise<M[]>;
......@@ -2114,7 +2104,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
*/
public static increment<M extends Model>(
this: ModelStatic<M>,
fields: (keyof M['_attributes'])[],
fields: ReadonlyArray<keyof M['_attributes']>,
options: IncrementDecrementOptionsWithBy<M['_attributes']>
): Promise<M>;
......@@ -2307,11 +2297,11 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
public static beforeBulkCreate<M extends Model>(
this: ModelStatic<M>,
name: string,
fn: (instances: M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
fn: (instances: readonly M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
): void;
public static beforeBulkCreate<M extends Model>(
this: ModelStatic<M>,
fn: (instances: M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
fn: (instances: readonly M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
): void;
/**
......@@ -2323,11 +2313,11 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
public static afterBulkCreate<M extends Model>(
this: ModelStatic<M>,
name: string,
fn: (instances: M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
fn: (instances: readonly M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
): void;
public static afterBulkCreate<M extends Model>(
this: ModelStatic<M>,
fn: (instances: M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
fn: (instances: readonly M[], options: BulkCreateOptions<M['_attributes']>) => HookReturn
): void;
/**
......@@ -2458,11 +2448,11 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
public static afterFind<M extends Model>(
this: ModelStatic<M>,
name: string,
fn: (instancesOrInstance: M[] | M | null, options: FindOptions<M['_attributes']>) => HookReturn
fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions<M['_attributes']>) => HookReturn
): void;
public static afterFind<M extends Model>(
this: ModelStatic<M>,
fn: (instancesOrInstance: M[] | M | null, options: FindOptions<M['_attributes']>) => HookReturn
fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions<M['_attributes']>) => HookReturn
): void;
/**
......@@ -2787,7 +2777,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
* If and object is provided, each column is incremented by the value given.
*/
public increment<K extends keyof TModelAttributes>(
fields: K | K[] | Partial<TModelAttributes>,
fields: K | readonly K[] | Partial<TModelAttributes>,
options?: IncrementDecrementOptionsWithBy<TModelAttributes>
): Promise<this>;
......@@ -2812,7 +2802,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
* If and object is provided, each column is decremented by the value given
*/
public decrement<K extends keyof TModelAttributes>(
fields: K | K[] | Partial<TModelAttributes>,
fields: K | readonly K[] | Partial<TModelAttributes>,
options?: IncrementDecrementOptionsWithBy<TModelAttributes>
): Promise<this>;
......@@ -2824,7 +2814,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
/**
* Check if this is eqaul to one of `others` by calling equals
*/
public equalsOneOf(others: this[]): boolean;
public equalsOneOf(others: readonly this[]): boolean;
/**
* Convert the instance to a JSON representation. Proxies to calling `get` with no keys. This means get all
......
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';
class MyModel extends Model {
public hi!: number;
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,127 +18,95 @@ 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 = {
[Op.and]: { a: 5 }, // AND (a = 5)
};
const typedAnd: AndOperator<{ a: number }> = {
[Op.and]: { a: 5 }, // AND (a = 5)
};
const or: OrOperator = {
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
};
const typedOr: OrOperator<{ a: number }> = {
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
};
let operators: WhereOperators = {
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
[Op.lte]: 10, // <= 10
[Op.ne]: 20, // != 20
[Op.not]: true, // IS NOT TRUE
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only)
[Op.startsWith]: 'hat',
[Op.endsWith]: 'hat',
[Op.substring]: 'hat',
[Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator)
[Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator)
[Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator)
[Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only)
[Op.regexp]: '^[h|a|t]', // 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.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 = {
expectTypeOf({
[Op.and]: { a: 5 }, // AND (a = 5)
}).toMatchTypeOf<AndOperator>();
expectTypeOf({
[Op.and]: { a: 5 }, // AND (a = 5)
}).toMatchTypeOf<AndOperator<{ a: number }>>();
expectTypeOf({
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
}).toMatchTypeOf<OrOperator>();
expectTypeOf({
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
}).toMatchTypeOf<OrOperator<{ a: number }>>();
expectTypeOf({
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
[Op.lte]: 10, // <= 10
[Op.ne]: 20, // != 20
[Op.not]: true, // IS NOT TRUE
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only)
[Op.startsWith]: 'hat',
[Op.endsWith]: 'hat',
[Op.substring]: 'hat',
[Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator)
[Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator)
[Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator)
[Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only)
[Op.regexp]: '^[h|a|t]', // 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.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only)
} 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.lt]: new Date(),
[Op.gt]: new Date(Date.now() - 24 * 60 * 60 * 1000),
}
},
{
[Op.or]: [
{
title: {
[Op.like]: 'Boat%',
},
},
{
description: {
[Op.like]: '%boat%',
},
},
],
};
// title LIKE 'Boat%' OR description LIKE '%boat%'
// Containment
where = {
{ title: { [Op.like]: 'Boat%' } },
{ description: { [Op.like]: '%boat%' } }
]
},
{
meta: {
[Op.contains]: {
site: {
url: 'http://google.com',
},
},
[Op.contains]: {
site: {
url: 'https://sequelize.org/'
}
}
},
meta2: {
[Op.contains]: ['stringValue1', 'stringValue2', 'stringValue3']
......@@ -151,31 +114,88 @@ 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
MyModel.findAll({
include: [
{
model: MyModel,
where: { state: Sequelize.col('project.state') },
},
],
include: [
{
model: MyModel,
where: { state: Sequelize.col('project.state') },
},
],
});
MyModel.findOne({
{
const where: WhereOptions = 0 as any;
MyModel.findOne({
include: [
{
include: [{ model: MyModel, where }],
model: MyModel,
where,
},
{
include: [{ model: MyModel, where }],
model: MyModel,
where,
},
],
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/
......@@ -197,141 +217,61 @@ async function test() {
}
MyModel.findAll({
where: {
id: {
// casting here to check a missing operator is not accepted as field name
[Op.and]: { a: 5 }, // AND (a = 5)
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
[Op.gt]: 6, // id > 6
[Op.gte]: 6, // id >= 6
[Op.lt]: 10, // id < 10
[Op.lte]: 10, // id <= 10
[Op.ne]: 20, // id != 20
[Op.between]: [6, 10] || [new Date(), new Date()], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only)
[Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator)
[Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator)
[Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator)
[Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only)
[Op.adjacent]: [1, 2],
[Op.strictLeft]: [1, 2],
[Op.strictRight]: [1, 2],
[Op.noExtendLeft]: [1, 2],
[Op.noExtendRight]: [1, 2],
[Op.values]: [1, 2],
} as WhereOperators,
status: {
[Op.not]: false, // status NOT FALSE
},
},
});
// Complex filtering / NOT queries
where = {
name: 'a project',
[Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }],
};
where = {
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,
},
},
// casting here to check a missing operator is not accepted as field name
[Op.and]: { a: 5 }, // AND (a = 5)
[Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6)
[Op.gt]: 6, // id > 6
[Op.gte]: 6, // id >= 6
[Op.lt]: 10, // id < 10
[Op.lte]: 10, // id <= 10
[Op.ne]: 20, // id != 20
[Op.between]: [6, 10] || [new Date(), new Date()], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only)
[Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator)
[Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator)
[Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator)
[Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only)
[Op.adjacent]: [1, 2],
[Op.strictLeft]: [1, 2],
[Op.strictRight]: [1, 2],
[Op.noExtendLeft]: [1, 2],
[Op.noExtendRight]: [1, 2],
[Op.values]: [1, 2],
} as WhereOperators,
status: {
[Op.not]: false, // status NOT FALSE
},
};
// 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'),
[Op.any]: Sequelize.literal('LIT'),
[Op.gte]: Sequelize.literal('LIT'),
[Op.lt]: Sequelize.literal('LIT'),
[Op.lte]: Sequelize.literal('LIT'),
[Op.ne]: Sequelize.literal('LIT'),
[Op.not]: Sequelize.literal('LIT'),
[Op.in]: Sequelize.literal('LIT'),
[Op.notIn]: Sequelize.literal('LIT'),
[Op.like]: Sequelize.literal('LIT'),
[Op.notLike]: Sequelize.literal('LIT'),
[Op.iLike]: Sequelize.literal('LIT'),
[Op.overlap]: Sequelize.literal('LIT'),
[Op.contains]: Sequelize.literal('LIT'),
[Op.contained]: Sequelize.literal('LIT'),
[Op.gt]: Sequelize.literal('LIT'),
[Op.notILike]: Sequelize.literal('LIT'),
}
Sequelize.cast(Sequelize.col('SOME_COL'), 'INTEGER'), {
[Op.lt]: Sequelize.literal('LIT'),
[Op.any]: Sequelize.literal('LIT'),
[Op.gte]: Sequelize.literal('LIT'),
[Op.lt]: Sequelize.literal('LIT'),
[Op.lte]: Sequelize.literal('LIT'),
[Op.ne]: Sequelize.literal('LIT'),
[Op.not]: Sequelize.literal('LIT'),
[Op.in]: Sequelize.literal('LIT'),
[Op.notIn]: Sequelize.literal('LIT'),
[Op.like]: Sequelize.literal('LIT'),
[Op.notLike]: Sequelize.literal('LIT'),
[Op.iLike]: Sequelize.literal('LIT'),
[Op.overlap]: Sequelize.literal('LIT'),
[Op.contains]: Sequelize.literal('LIT'),
[Op.contained]: Sequelize.literal('LIT'),
[Op.gt]: Sequelize.literal('LIT'),
[Op.notILike]: Sequelize.literal('LIT'),
}
)
Sequelize.where(Sequelize.col("ABS"), Op.is, null);
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!