mirror of
https://github.com/gosticks/DefinitelyTyped.git
synced 2025-10-16 12:05:41 +00:00
[@types/redux-orm] add six overloads for createSelector and fix invalid return type of createReducer (#36738)
This commit is contained in:
parent
bd4aef3a75
commit
cef01b83fb
12
types/redux-orm/Model.d.ts
vendored
12
types/redux-orm/Model.d.ts
vendored
@ -37,7 +37,7 @@ export interface SerializableMap {
|
||||
* - {@link MutableQuerySet} - for many-to-many relations
|
||||
* - {@link QuerySet} - for reverse side of foreign keys
|
||||
*/
|
||||
export type ModelField = MutableQuerySet | QuerySet | SessionBoundModel | Serializable;
|
||||
export type ModelField = QuerySet | SessionBoundModel | Serializable;
|
||||
|
||||
/**
|
||||
* Map of fields restriction to supported field types.
|
||||
@ -72,7 +72,7 @@ export type IdOrModelLike<M extends Model> = IdType<M> | {getId(): IdType<M>; };
|
||||
* logic by defining prototype methods (without `static` keyword).
|
||||
* @borrows {@link QuerySet.filter} as Model#filter
|
||||
*/
|
||||
export default class Model<MClass extends typeof AnyModel = any, Fields extends ModelFieldMap = any> {
|
||||
export default class Model<MClass extends typeof AnyModel = typeof AnyModel, Fields extends ModelFieldMap = any> {
|
||||
/**
|
||||
* A string constant identifying specific Model, necessary to retain the shape of state and relations through transpilation steps
|
||||
*/
|
||||
@ -122,7 +122,7 @@ export default class Model<MClass extends typeof AnyModel = any, Fields extends
|
||||
*
|
||||
* @return a reference to the plain JS object in the store
|
||||
*/
|
||||
readonly ref: Ref<InstanceType<MClass>>;
|
||||
readonly ref: Ref<this>;
|
||||
|
||||
/**
|
||||
* Creates a Model instance from it's properties.
|
||||
@ -402,7 +402,7 @@ export type IdType<M extends Model> = IdKey<M> extends infer U
|
||||
/**
|
||||
* Type of {@link Model.ref} / database entry for a particular Model type
|
||||
*/
|
||||
export type Ref<M extends Model> = {
|
||||
export type Ref<M extends AnyModel> = {
|
||||
[K in keyof RefFields<M>]: ModelFields<M>[K] extends AnyModel ? IdType<ModelFields<M>[K]> : RefFields<M>[K]
|
||||
};
|
||||
|
||||
@ -412,7 +412,7 @@ export type Ref<M extends Model> = {
|
||||
* - declared Model field type - if propertyName belongs to declared Model fields
|
||||
* - any serializable value - if propertyName is not among declared Model fields
|
||||
*/
|
||||
export type RefPropOrSimple<M extends Model, K extends string> = K extends keyof RefFields<M>
|
||||
export type RefPropOrSimple<M extends AnyModel, K extends string> = K extends keyof RefFields<M>
|
||||
? Ref<M>[K]
|
||||
: Serializable;
|
||||
|
||||
@ -477,7 +477,7 @@ export type ModelClass<M extends AnyModel> = ReturnType<M['getClass']>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type ModelFields<M extends Model> = [ConstructorParameters<ModelClass<M>>] extends [[infer U]]
|
||||
export type ModelFields<M extends Model> = ConstructorParameters<ModelClass<M>> extends [infer U]
|
||||
? U extends ModelFieldMap
|
||||
? U
|
||||
: never
|
||||
|
||||
4
types/redux-orm/ORM.d.ts
vendored
4
types/redux-orm/ORM.d.ts
vendored
@ -10,7 +10,7 @@ import Session, { OrmSession } from './Session';
|
||||
* - ORM branch state type
|
||||
*/
|
||||
export type IndexedModelClasses<
|
||||
T extends { [k in keyof T]: typeof AnyModel },
|
||||
T extends { [k in keyof T]: typeof AnyModel } = {},
|
||||
K extends keyof T = Extract<keyof T, T[keyof T]['modelName']>
|
||||
> = { [k in K]: T[K] };
|
||||
|
||||
@ -88,7 +88,7 @@ export class ORM<I extends IndexedModelClasses<any>, ModelNames extends keyof I
|
||||
*
|
||||
* @return a new {@link Session} instance
|
||||
*/
|
||||
session(state: OrmState<I>): OrmSession<I>;
|
||||
session(state?: OrmState<I>): OrmSession<I>;
|
||||
|
||||
/**
|
||||
* Begins an mutable database session.
|
||||
|
||||
@ -125,36 +125,36 @@ const sessionFixture = () => {
|
||||
return orm.session(orm.getEmptyState());
|
||||
};
|
||||
|
||||
// inferred optionality of ModelType.create argument properties
|
||||
const argOptionalityAtModelCreation = () => {
|
||||
const { Book, Publisher, Person } = sessionFixture();
|
||||
// argOptionalityAtModelCreation - inferred optionality of ModelType.create argument properties
|
||||
(() => {
|
||||
const { Book, Publisher } = sessionFixture();
|
||||
|
||||
/**
|
||||
* 1.A. `number` Model identifiers are optional due to built-in incremental sequencing of numeric identifiers
|
||||
* @see {@link PublisherFields.index}
|
||||
*/
|
||||
const publisher = Publisher.create({ name: 'P1' });
|
||||
Publisher.create({ name: 'P1' });
|
||||
|
||||
/**
|
||||
* 1.B. `string` identifiers are mandatory
|
||||
*/
|
||||
const stringIdMissing = Book.create({ publisher: 1, coverArt: 'foo.bmp' }); // $ExpectError
|
||||
Book.create({ publisher: 1, coverArt: 'foo.bmp' }); // $ExpectError
|
||||
|
||||
/**
|
||||
* 2. non-relational fields with corresponding descriptors that contain defined `getDefault` callback: (`attr({ getDefault: () => 'empty.png' })`)
|
||||
* @see {@link Book#fields.coverArt}
|
||||
*/
|
||||
const book2 = Book.create({ title: 'B2', publisher: 1 });
|
||||
Book.create({ title: 'B2', publisher: 1 });
|
||||
|
||||
/**
|
||||
* 3. both attribute and relational fields where corresponding ModelFields interface property has optional (`?`) modifier
|
||||
* @see {@link BookFields.authors}
|
||||
*/
|
||||
const book1 = Book.create({ title: 'B1', publisher: 1, coverArt: 'foo.bmp' });
|
||||
};
|
||||
Book.create({ title: 'B1', publisher: 1, coverArt: 'foo.bmp' });
|
||||
})();
|
||||
|
||||
// ModelFields contribute to type constraints within ModelType.create arguments
|
||||
const argPropertyTypeRestrictionsOnCreate = () => {
|
||||
// argPropertyTypeRestrictionsOnCreate - ModelFields contribute to type constraints within ModelType.create arguments
|
||||
(() => {
|
||||
const { Book, Publisher, Person } = sessionFixture();
|
||||
|
||||
/** Keys of declared model fields interface contribute strict requirements regarding corresponding property types */
|
||||
@ -180,7 +180,7 @@ const argPropertyTypeRestrictionsOnCreate = () => {
|
||||
Book.create({ title: 'B1', publisher: publisherModel, authors: [authorModel] });
|
||||
Book.create({
|
||||
title: 'B1',
|
||||
publisher: publisherModel.index ,
|
||||
publisher: publisherModel.index,
|
||||
authors: [authorModel, 'A1', authorModel, authorModel.ref.id]
|
||||
});
|
||||
|
||||
@ -189,10 +189,10 @@ const argPropertyTypeRestrictionsOnCreate = () => {
|
||||
Book.create({ title: 'B1', publisher: publisherModel.ref, authors: [publisherModel.ref, 'A1'] }); // $ExpectError
|
||||
Book.create({ title: 'B1', publisher: { index: 'P1 ' } }); // $ExpectError
|
||||
Book.create({ title: 'B1', publisher: { index: 0 }, authors: [authorModel, true] }); // $ExpectError
|
||||
};
|
||||
})();
|
||||
|
||||
// ModelFields contribute to type constraints within ModelType.create arguments
|
||||
const argPropertyTypeRestrictionsOnUpsert = () => {
|
||||
// argPropertyTypeRestrictionsOnUpsert - ModelFields contribute to type constraints within ModelType.create arguments
|
||||
(() => {
|
||||
const { Book, Publisher, Person } = sessionFixture();
|
||||
|
||||
/** Upsert requires id to be provided */
|
||||
@ -228,45 +228,46 @@ const argPropertyTypeRestrictionsOnUpsert = () => {
|
||||
Book.create({ title: 'B1', publisher: publisherModel.ref, authors: [publisherModel.ref, 'A1'] }); // $ExpectError
|
||||
Book.create({ title: 'B1', publisher: { index: 'P1 ' } }); // $ExpectError
|
||||
Book.create({ title: 'B1', publisher: { index: 0 }, authors: [authorModel, true] }); // $ExpectError
|
||||
};
|
||||
})();
|
||||
|
||||
// restriction of allowed ORM.register args
|
||||
const restrictRegisterArgsToSchemaModels = () => {
|
||||
(() => {
|
||||
const incompleteSchema = { Book, Authorship, Person };
|
||||
const orm = new ORM<typeof incompleteSchema>();
|
||||
orm.register(Book, Authorship, Person, Publisher); // $ExpectError
|
||||
};
|
||||
})();
|
||||
|
||||
// inference of ORM branch state type
|
||||
const inferOrmBranchEmptyState = () => {
|
||||
(() => {
|
||||
const emptyState = ormFixture().getEmptyState();
|
||||
|
||||
const bookTableState = emptyState.Book; // $ExpectType TableState<typeof Book>
|
||||
const bookItemsById = emptyState.Book.itemsById; // $ExpectType { readonly [K: string]: Ref<Book>; }
|
||||
const authorshipMetaState = emptyState.Authorship.meta.maxId; // $ExpectType number
|
||||
const bookMetaState = emptyState.Book.meta.maxId; // $ExpectType number | null
|
||||
};
|
||||
})();
|
||||
|
||||
// indexing session instance using registered Model.modelName returns narrowed Model class
|
||||
const sessionInstanceExtendedWithNarrowedModelClasses = () => {
|
||||
// sessionInstanceExtendedWithNarrowedModelClasses - indexing session instance using registered Model.modelName returns narrowed Model class
|
||||
(() => {
|
||||
const { Book, Person, Publisher } = sessionFixture();
|
||||
|
||||
// $ExpectType { Book: ModelType<Book>; Person: ModelType<Person>; Publisher: ModelType<Publisher>; }
|
||||
const sessionBoundModels = { Book, Person, Publisher };
|
||||
};
|
||||
return { ...sessionBoundModels };
|
||||
})();
|
||||
|
||||
// IdKey and IdType mapped types support for valid identifier configurations
|
||||
const idInferenceAndCustomizations = () => {
|
||||
(() => {
|
||||
type ExtractId<M extends Model> = [IdKey<M>, IdType<M>];
|
||||
|
||||
type ImplicitDefault = ExtractId<Authorship>; // $ExpectType ["id", number]
|
||||
type CustomKey = ExtractId<Publisher>; // $ExpectType ["index", number]
|
||||
type CustomType = ExtractId<Person>; // $ExpectType ["id", string]
|
||||
type CustomKeyAndType = ExtractId<Book>; // $ExpectType ["title", string]
|
||||
};
|
||||
})();
|
||||
|
||||
// Model#create result retains custom properties supplied during call
|
||||
const customInstanceProperties = () => {
|
||||
(() => {
|
||||
const { Book } = sessionFixture();
|
||||
|
||||
const basicBook = Book.create({ title: 'book', publisher: 1 });
|
||||
@ -287,30 +288,31 @@ const customInstanceProperties = () => {
|
||||
type customBookKeys = Exclude<keyof typeof extendedBook, keyof Model>; // $ExpectType "title" | "coverArt" | "publisher" | "authors" | "customProp"
|
||||
const extendedBookTitle = extendedBook.title; // $ExpectType string
|
||||
const instanceCustomProp = extendedBook.customProp; // $ExpectType { foo: number; bar: boolean; }
|
||||
};
|
||||
})();
|
||||
|
||||
// reducer API is intact
|
||||
const standaloneReducerFunction = () => {
|
||||
(() => {
|
||||
const orm = ormFixture();
|
||||
|
||||
type StateType = OrmState<Schema>;
|
||||
|
||||
const reducerAddItem = (state: StateType, action: CreateBookAction): StateType => {
|
||||
return (state: StateType, action: CreateBookAction): StateType => {
|
||||
const session = orm.session(state);
|
||||
session.Book.create(action.payload);
|
||||
return session.state;
|
||||
};
|
||||
};
|
||||
})();
|
||||
|
||||
// QuerySet type is retained though query chain until terminated.
|
||||
// Orders are optional, must conform to SortOrder type when present.
|
||||
// QuerySet.orderBy overloads accept iteratees applicable to QuerySet's type only
|
||||
const orderByArguments = () => {
|
||||
// orderByArguments
|
||||
(() => {
|
||||
const { Book } = sessionFixture();
|
||||
const booksQuerySet = Book.all();
|
||||
|
||||
// $ExpectType readonly Ref<Book>[]
|
||||
const singleIteratee = booksQuerySet
|
||||
booksQuerySet
|
||||
.orderBy('title')
|
||||
.orderBy(book => book.publisher, 'desc')
|
||||
.orderBy(book => book.title, false)
|
||||
@ -319,7 +321,7 @@ const orderByArguments = () => {
|
||||
.toRefArray();
|
||||
|
||||
// $ExpectType readonly Ref<Book>[]
|
||||
const arrayIteratee = booksQuerySet
|
||||
booksQuerySet
|
||||
.orderBy(['title'], ['asc'])
|
||||
.orderBy(['publisher', 'title'], [true, 'desc'])
|
||||
.orderBy([book => book.title], ['desc'])
|
||||
@ -327,17 +329,18 @@ const orderByArguments = () => {
|
||||
.orderBy([book => book.title, 'publisher'], ['desc', false])
|
||||
.toRefArray();
|
||||
|
||||
const invalidSingleKeyIteratee = booksQuerySet.orderBy('notABookPropertyKey'); // $ExpectError
|
||||
const invalidSingleFunctionIteratee = booksQuerySet.orderBy([book => book.notABookPropertyKey], false); // $ExpectError
|
||||
const invalidStringOrderDirectionType = booksQuerySet.orderBy('title', 'inc'); // $ExpectError
|
||||
const invalidSingleOrderDirectionType = booksQuerySet.orderBy('title', 4); // $ExpectError
|
||||
const invalidArrayKeyIteratee = booksQuerySet.orderBy(['notABookPropertyKey']); // $ExpectError
|
||||
const invalidArrayFunctionIteratee = booksQuerySet.orderBy([book => book.notABookPropertyKey]); // $ExpectError
|
||||
const invalidArrayStringOrderDirection = booksQuerySet.orderBy(['title'], ['inc']); // $ExpectError
|
||||
const invalidArrayOrderDirectionType = booksQuerySet.orderBy(['title'], [4]); // $ExpectError
|
||||
};
|
||||
booksQuerySet.orderBy('notABookPropertyKey'); // $ExpectError
|
||||
booksQuerySet.orderBy([book => book.notABookPropertyKey], false); // $ExpectError
|
||||
booksQuerySet.orderBy('title', 'inc'); // $ExpectError
|
||||
booksQuerySet.orderBy('title', 4); // $ExpectError
|
||||
booksQuerySet.orderBy(['notABookPropertyKey']); // $ExpectError
|
||||
booksQuerySet.orderBy([book => book.notABookPropertyKey]); // $ExpectError
|
||||
booksQuerySet.orderBy(['title'], ['inc']); // $ExpectError
|
||||
booksQuerySet.orderBy(['title'], [4]); // $ExpectError
|
||||
})();
|
||||
|
||||
const selectors = () => {
|
||||
// selectors
|
||||
(() => {
|
||||
// test fixture, use reselect.createSelector in production code
|
||||
const createSelector = <S, OS extends OrmState<any>, Result extends any>(
|
||||
param1Creator: (state: S) => OS,
|
||||
@ -363,4 +366,97 @@ const selectors = () => {
|
||||
);
|
||||
|
||||
selector({ db: orm.getEmptyState() }); // $ExpectType Ref<Book>
|
||||
};
|
||||
})();
|
||||
|
||||
// advanced selectors
|
||||
(() => {
|
||||
const orm = ormFixture();
|
||||
|
||||
interface RootState {
|
||||
foo: number;
|
||||
bar: string;
|
||||
db: OrmState<Schema>;
|
||||
}
|
||||
|
||||
type TestSelector = (state: RootState) => Ref<Book>;
|
||||
|
||||
const selector1 = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.bar,
|
||||
(session, title) => session.Book.get({ title })!.ref
|
||||
) as TestSelector;
|
||||
|
||||
const selector2 = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
(session, id, title) => session.Book.get({ id, title })!.ref
|
||||
) as TestSelector;
|
||||
|
||||
const selector3 = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
s => s.foo,
|
||||
(session, id, title, id2) => session.Book.get({ id, title, id2 })!.ref
|
||||
) as TestSelector;
|
||||
|
||||
const selector4 = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
(session, id, title, id2, title2) => session.Book.get({ id, title, id2, title2 })!.ref
|
||||
) as TestSelector;
|
||||
|
||||
const selector5 = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
s => s.foo,
|
||||
(session, ...args) => session.Book.get({ title: args[1] })!.ref
|
||||
) as TestSelector;
|
||||
|
||||
const selector6 = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
s => s.foo,
|
||||
s => s.bar,
|
||||
(session, id, title) => session.Book.get({ title })!.ref
|
||||
) as TestSelector;
|
||||
|
||||
const invalidSelector = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.foo,
|
||||
(session, foo, missingArg) => foo // $ExpectError
|
||||
) as (state: RootState) => number;
|
||||
|
||||
const invalidSelector2: TestSelector = createOrmSelector(
|
||||
orm,
|
||||
s => s.db,
|
||||
s => s.foo,
|
||||
(session, foo) => session.Book.withId(foo)!.ref // $ExpectError
|
||||
);
|
||||
|
||||
const state = { db: orm.getEmptyState(), foo: 1, bar: 'foo' };
|
||||
|
||||
selector1(state); // $ExpectType Ref<Book>
|
||||
selector2(state); // $ExpectType Ref<Book>
|
||||
selector3(state); // $ExpectType Ref<Book>
|
||||
selector4(state); // $ExpectType Ref<Book>
|
||||
selector5(state); // $ExpectType Ref<Book>
|
||||
selector6(state); // $ExpectType Ref<Book>
|
||||
})();
|
||||
|
||||
74
types/redux-orm/redux.d.ts
vendored
74
types/redux-orm/redux.d.ts
vendored
@ -1,25 +1,81 @@
|
||||
import { IndexedModelClasses, ORM, OrmState } from './ORM';
|
||||
import { OrmSession } from './Session';
|
||||
|
||||
export interface ORMReducer<I extends IndexedModelClasses<any>, TAction extends any = any> {
|
||||
(state: OrmState<I> | undefined, action: TAction): OrmSession<I>;
|
||||
export interface ORMReducer<I extends IndexedModelClasses, TAction extends any = any> {
|
||||
(state: OrmState<I> | undefined, action: TAction): OrmState<I>;
|
||||
}
|
||||
|
||||
export type defaultUpdater<I extends IndexedModelClasses<any>, TAction extends any = any> = (
|
||||
export type defaultUpdater<I extends IndexedModelClasses, TAction extends any = any> = (
|
||||
session: OrmSession<I>,
|
||||
action: TAction
|
||||
) => void;
|
||||
|
||||
export function createReducer<I extends IndexedModelClasses<any>, TAction extends any = any>(
|
||||
export function createReducer<I extends IndexedModelClasses, TAction extends any = any>(
|
||||
orm: ORM<I>,
|
||||
updater?: defaultUpdater<I, TAction>
|
||||
): ORMReducer<I, TAction>;
|
||||
|
||||
export interface ORMSelector<I extends IndexedModelClasses<any>, Result extends any = any> {
|
||||
(session: OrmSession<I>, ...args: any[]): Result;
|
||||
export type Selector<S, R> = (state: S) => R;
|
||||
|
||||
export interface ORMSelector<I extends IndexedModelClasses, Args extends any[], R> {
|
||||
(session: OrmSession<I>, ...args: Args): R;
|
||||
}
|
||||
|
||||
export function createSelector<I extends IndexedModelClasses<any>, Result extends any = any>(
|
||||
export function createSelector<S, I, R1, R2, R3, R4, R5, R6, R>(
|
||||
orm: ORM<I>,
|
||||
ormSelector: ORMSelector<I, Result>
|
||||
): (state: OrmState<I>) => Result;
|
||||
ormStateSelector: Selector<S, OrmState<I>>,
|
||||
selector1: Selector<S, R1>,
|
||||
selector2: Selector<S, R2>,
|
||||
selector3: Selector<S, R3>,
|
||||
selector4: Selector<S, R4>,
|
||||
selector5: Selector<S, R5>,
|
||||
selector6: Selector<S, R6>,
|
||||
ormSelector: ORMSelector<I, [R1, R2, R3, R4, R5, R6], R>
|
||||
): Selector<S, R>;
|
||||
|
||||
export function createSelector<S, I, R1, R2, R3, R4, R5, R>(
|
||||
orm: ORM<I>,
|
||||
ormStateSelector: Selector<S, OrmState<I>>,
|
||||
selector1: Selector<S, R1>,
|
||||
selector2: Selector<S, R2>,
|
||||
selector3: Selector<S, R3>,
|
||||
selector4: Selector<S, R4>,
|
||||
selector5: Selector<S, R5>,
|
||||
ormSelector: ORMSelector<I, [R1, R2, R3, R4, R5], R>
|
||||
): Selector<S, R>;
|
||||
|
||||
export function createSelector<S, I, R1, R2, R3, R4, R>(
|
||||
orm: ORM<I>,
|
||||
ormStateSelector: Selector<S, OrmState<I>>,
|
||||
selector1: Selector<S, R1>,
|
||||
selector2: Selector<S, R2>,
|
||||
selector3: Selector<S, R3>,
|
||||
selector4: Selector<S, R4>,
|
||||
ormSelector: ORMSelector<I, [R1, R2, R3, R4], R>
|
||||
): Selector<S, R>;
|
||||
|
||||
export function createSelector<S, I, R1, R2, R3, R>(
|
||||
orm: ORM<I>,
|
||||
ormStateSelector: Selector<S, OrmState<I>>,
|
||||
selector1: Selector<S, R1>,
|
||||
selector2: Selector<S, R2>,
|
||||
selector3: Selector<S, R3>,
|
||||
ormSelector: ORMSelector<I, [R1, R2, R3], R>
|
||||
): Selector<S, R>;
|
||||
|
||||
export function createSelector<S, I, R1, R2, R>(
|
||||
orm: ORM<I>,
|
||||
ormStateSelector: Selector<S, OrmState<I>>,
|
||||
selector1: Selector<S, R1>,
|
||||
selector2: Selector<S, R2>,
|
||||
ormSelector: ORMSelector<I, [R1, R2], R>
|
||||
): Selector<S, R>;
|
||||
|
||||
export function createSelector<S, I, R1, R>(
|
||||
orm: ORM<I>,
|
||||
ormStateSelector: Selector<S, OrmState<I>>,
|
||||
selector1: Selector<S, R1>,
|
||||
ormSelector: ORMSelector<I, [R1], R>
|
||||
): Selector<S, R>;
|
||||
|
||||
export function createSelector<I, R>(orm: ORM<I>, ormSelector: ORMSelector<I, [], R>): Selector<OrmState<I>, R>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user