Skip to content

Commit

Permalink
fix(typings): make findX model methods return custom attributes if …
Browse files Browse the repository at this point in the history
…`raw` is `true` (#12921)
  • Loading branch information
JacobLey committed Jun 16, 2022
1 parent 2f63c86 commit 3cf2207
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 5 deletions.
31 changes: 28 additions & 3 deletions src/model.d.ts
Expand Up @@ -936,6 +936,9 @@ export interface NonNullFindOptions<TAttributes = any> extends FindOptions<TAttr
rejectOnEmpty: true | Error;
}

export interface FindByPkOptions<M extends Model> extends Omit<FindOptions<Attributes<M>>, 'where'> {}

export interface NonNullFindByPkOptions<M extends Model> extends Omit<NonNullFindOptions<Attributes<M>>, 'where'> {}
/**
* Options for Model.count method
*/
Expand Down Expand Up @@ -2423,9 +2426,13 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
*
* @returns A promise that will resolve with the array containing the results of the SELECT query.
*/
public static findAll<M extends Model, R = Attributes<M>>(
this: ModelStatic<M>,
options?: Omit<FindOptions<Attributes<M>>, 'raw'> & { raw: true },
): Promise<R[]>;
public static findAll<M extends Model>(
this: ModelStatic<M>,
options?: FindOptions<Attributes<M>>
options?: FindOptions<Attributes<M>>,
): Promise<M[]>;

/**
Expand All @@ -2436,15 +2443,25 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
* Returns the model with the matching primary key.
* If not found, returns null or throws an error if {@link FindOptions.rejectOnEmpty} is set.
*/
public static findByPk<M extends Model, R = Attributes<M>>(
this: ModelStatic<M>,
identifier: Identifier,
options: FindByPkOptions<M> & { raw: true, rejectOnEmpty?: false }
): Promise<R | null>;
public static findByPk<M extends Model, R = Attributes<M>>(
this: ModelStatic<M>,
identifier: Identifier,
options: NonNullFindByPkOptions<M> & { raw: true }
): Promise<R>;
public static findByPk<M extends Model>(
this: ModelStatic<M>,
identifier: Identifier,
options: Omit<NonNullFindOptions<Attributes<M>>, 'where'>
options: NonNullFindByPkOptions<M>
): Promise<M>;
public static findByPk<M extends Model>(
this: ModelStatic<M>,
identifier?: Identifier,
options?: Omit<FindOptions<Attributes<M>>, 'where'>
options?: FindByPkOptions<M>
): Promise<M | null>;

/**
Expand All @@ -2453,6 +2470,14 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
* Returns the first instance corresponding matching the query.
* If not found, returns null or throws an error if {@link FindOptions.rejectOnEmpty} is set.
*/
public static findOne<M extends Model, R = Attributes<M>>(
this: ModelStatic<M>,
options: FindOptions<Attributes<M>> & { raw: true, rejectOnEmpty?: false }
): Promise<R | null>;
public static findOne<M extends Model, R = Attributes<M>>(
this: ModelStatic<M>,
options: NonNullFindOptions<Attributes<M>> & { raw: true }
): Promise<R>;
public static findOne<M extends Model>(
this: ModelStatic<M>,
options: NonNullFindOptions<Attributes<M>>
Expand Down
28 changes: 28 additions & 0 deletions test/types/findAll.ts
@@ -0,0 +1,28 @@
import { expectTypeOf } from 'expect-type';
import { Attributes, col } from '@sequelize/core';
import { User } from './models/User';

(async () => {
const users = await User.findAll({ raw: false });
expectTypeOf(users).toEqualTypeOf<User[]>();

const rawUsers = await User.findAll({ raw: true });
expectTypeOf(rawUsers).toEqualTypeOf<Attributes<User>[]>();

interface CustomUser {
foo: 'bar';
}

const customUsers = await User.findAll<User, CustomUser>({
attributes: [
['bar', 'foo'],
'ignored',
[col('table.id'), 'xyz'],
],
raw: true,
});
expectTypeOf(customUsers).toEqualTypeOf<CustomUser[]>();

// @ts-expect-error
customUsers[0].id = 123; // not an instance
})();
95 changes: 94 additions & 1 deletion test/types/findByPk.ts
@@ -1,9 +1,12 @@
import { expectTypeOf } from 'expect-type'
import { Attributes, FindByPkOptions } from '@sequelize/core';
import { User } from './models/User';
import { expectTypeOf } from 'expect-type';

(async () => {
expectTypeOf(await User.findByPk(Buffer.from('asdf'))).toEqualTypeOf<User | null>();

// rejectOnEmpty

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
rejectOnEmpty: undefined
})).toEqualTypeOf<User | null>();
Expand All @@ -19,4 +22,94 @@ import { expectTypeOf } from 'expect-type';
expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
rejectOnEmpty: new Error('')
})).toEqualTypeOf<User>();

// raw

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: true,
})).toEqualTypeOf<Attributes<User> | null>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: false,
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: undefined,
})).toEqualTypeOf<User | null>();

// combination

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: false,
rejectOnEmpty: false
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: false,
rejectOnEmpty: true
})).toEqualTypeOf<User>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: false,
rejectOnEmpty: undefined,
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: true,
rejectOnEmpty: false,
})).toEqualTypeOf<Attributes<User> | null>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: true,
rejectOnEmpty: true,
})).toEqualTypeOf<Attributes<User>>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: true,
rejectOnEmpty: undefined,
})).toEqualTypeOf<Attributes<User> | null>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: undefined,
rejectOnEmpty: false
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: undefined,
rejectOnEmpty: true
})).toEqualTypeOf<User>();

expectTypeOf(await User.findByPk(Buffer.from('asdf'), {
raw: undefined,
rejectOnEmpty: undefined,
})).toEqualTypeOf<User | null>();

// custom parameter

interface CustomUser {
foo: 'bar';
}

expectTypeOf(await User.findByPk<User, CustomUser>(Buffer.from('asdf'), {
raw: true,
})).toEqualTypeOf<CustomUser | null>();

expectTypeOf(await User.findByPk<User, CustomUser>(Buffer.from('asdf'), {
attributes: [['bar', 'foo']],
rejectOnEmpty: false,
raw: true,
})).toEqualTypeOf<CustomUser | null>();

expectTypeOf(await User.findByPk<User, CustomUser>(Buffer.from('asdf'), {
attributes: [['bar', 'foo']],
rejectOnEmpty: true,
raw: true,
})).toEqualTypeOf<CustomUser>();

async function passDown(params: FindByPkOptions<User>) {
// Unknown ahead of time
// We can't what 'rejectOnEmpty' and 'raw' are set to, so we default to these types:
expectTypeOf(await User.findByPk(Buffer.from('asdf'), params))
.toEqualTypeOf<User | null>();
}
})();
114 changes: 114 additions & 0 deletions test/types/findOne.ts
@@ -1,3 +1,5 @@
import { expectTypeOf } from 'expect-type';
import { Attributes, FindOptions } from '@sequelize/core';
import { User } from './models/User';

// These attributes exist
Expand All @@ -22,3 +24,115 @@ User.findOne({ where: { '$include1.includeAttr$': 'blah2' } });
// note: this *must* be a ts-ignore, as it works in ts >= 4.4
// @ts-ignore
User.findOne({ where: { '$include1.$include2.includeAttr$': 'blah2' } });

(async () => {
expectTypeOf(await User.findOne()).toEqualTypeOf<User | null>();

// rejectOnEmpty

expectTypeOf(await User.findOne({
rejectOnEmpty: undefined
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findOne({
rejectOnEmpty: false
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findOne({
rejectOnEmpty: true
})).toEqualTypeOf<User>();

expectTypeOf(await User.findOne({
rejectOnEmpty: new Error('')
})).toEqualTypeOf<User>();

// raw

expectTypeOf(await User.findOne({
raw: true,
})).toEqualTypeOf<Attributes<User> | null>();

expectTypeOf(await User.findOne({
raw: false,
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findOne({
raw: undefined,
})).toEqualTypeOf<User | null>();

// combination

expectTypeOf(await User.findOne({
raw: false,
rejectOnEmpty: false
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findOne({
raw: false,
rejectOnEmpty: true
})).toEqualTypeOf<User>();

expectTypeOf(await User.findOne({
raw: false,
rejectOnEmpty: undefined,
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findOne({
raw: true,
rejectOnEmpty: false,
})).toEqualTypeOf<Attributes<User> | null>();

expectTypeOf(await User.findOne({
raw: true,
rejectOnEmpty: true,
})).toEqualTypeOf<Attributes<User>>();

expectTypeOf(await User.findOne({
raw: true,
rejectOnEmpty: undefined,
})).toEqualTypeOf<Attributes<User> | null>();

expectTypeOf(await User.findOne({
raw: undefined,
rejectOnEmpty: false
})).toEqualTypeOf<User | null>();

expectTypeOf(await User.findOne({
raw: undefined,
rejectOnEmpty: true
})).toEqualTypeOf<User>();

expectTypeOf(await User.findOne({
raw: undefined,
rejectOnEmpty: undefined,
})).toEqualTypeOf<User | null>();

// custom parameter

interface CustomUser {
foo: 'bar';
}

expectTypeOf(await User.findOne<User, CustomUser>({
raw: true,
})).toEqualTypeOf<CustomUser | null>();

expectTypeOf(await User.findOne<User, CustomUser>({
attributes: [['bar', 'foo']],
rejectOnEmpty: false,
raw: true,
})).toEqualTypeOf<CustomUser | null>();

expectTypeOf(await User.findOne<User, CustomUser>({
attributes: [['bar', 'foo']],
rejectOnEmpty: true,
raw: true,
})).toEqualTypeOf<CustomUser>();

async function passDown(params: FindOptions<User>) {
// Unknown ahead of time
// We can't what 'rejectOnEmpty' and 'raw' are set to, so we default to these types:
expectTypeOf(await User.findOne(params))
.toEqualTypeOf<User | null>();
}
})();
2 changes: 1 addition & 1 deletion test/types/where.ts
Expand Up @@ -149,8 +149,8 @@ MyModel.findAll({
},
});

// @ts-expect-error - no attribute
MyModel.findAll({
// @ts-expect-error - no attribute
where: [1, 2],
});

Expand Down

0 comments on commit 3cf2207

Please sign in to comment.