diff --git a/types/redux-orm/Model.d.ts b/types/redux-orm/Model.d.ts index 8770786d97..d5e083ab88 100644 --- a/types/redux-orm/Model.d.ts +++ b/types/redux-orm/Model.d.ts @@ -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 = IdType | {getId(): IdType; }; * logic by defining prototype methods (without `static` keyword). * @borrows {@link QuerySet.filter} as Model#filter */ -export default class Model { +export default class Model { /** * 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>; + readonly ref: Ref; /** * Creates a Model instance from it's properties. @@ -402,7 +402,7 @@ export type IdType = IdKey extends infer U /** * Type of {@link Model.ref} / database entry for a particular Model type */ -export type Ref = { +export type Ref = { [K in keyof RefFields]: ModelFields[K] extends AnyModel ? IdType[K]> : RefFields[K] }; @@ -412,7 +412,7 @@ export type Ref = { * - 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 = K extends keyof RefFields +export type RefPropOrSimple = K extends keyof RefFields ? Ref[K] : Serializable; @@ -477,7 +477,7 @@ export type ModelClass = ReturnType; /** * @internal */ -export type ModelFields = [ConstructorParameters>] extends [[infer U]] +export type ModelFields = ConstructorParameters> extends [infer U] ? U extends ModelFieldMap ? U : never diff --git a/types/redux-orm/ORM.d.ts b/types/redux-orm/ORM.d.ts index db60cd0681..6fd7832ba2 100644 --- a/types/redux-orm/ORM.d.ts +++ b/types/redux-orm/ORM.d.ts @@ -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 > = { [k in K]: T[K] }; @@ -88,7 +88,7 @@ export class ORM, ModelNames extends keyof I * * @return a new {@link Session} instance */ - session(state: OrmState): OrmSession; + session(state?: OrmState): OrmSession; /** * Begins an mutable database session. diff --git a/types/redux-orm/redux-orm-tests.ts b/types/redux-orm/redux-orm-tests.ts index aeefb4ddb6..ce40c681e0 100644 --- a/types/redux-orm/redux-orm-tests.ts +++ b/types/redux-orm/redux-orm-tests.ts @@ -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(); 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 const bookItemsById = emptyState.Book.itemsById; // $ExpectType { readonly [K: string]: Ref; } 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; Person: ModelType; Publisher: ModelType; } const sessionBoundModels = { Book, Person, Publisher }; -}; + return { ...sessionBoundModels }; +})(); // IdKey and IdType mapped types support for valid identifier configurations -const idInferenceAndCustomizations = () => { +(() => { type ExtractId = [IdKey, IdType]; type ImplicitDefault = ExtractId; // $ExpectType ["id", number] type CustomKey = ExtractId; // $ExpectType ["index", number] type CustomType = ExtractId; // $ExpectType ["id", string] type CustomKeyAndType = ExtractId; // $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; // $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; - 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[] - 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[] - 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 = , Result extends any>( param1Creator: (state: S) => OS, @@ -363,4 +366,97 @@ const selectors = () => { ); selector({ db: orm.getEmptyState() }); // $ExpectType Ref -}; +})(); + +// advanced selectors +(() => { + const orm = ormFixture(); + + interface RootState { + foo: number; + bar: string; + db: OrmState; + } + + type TestSelector = (state: RootState) => Ref; + + 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 + selector2(state); // $ExpectType Ref + selector3(state); // $ExpectType Ref + selector4(state); // $ExpectType Ref + selector5(state); // $ExpectType Ref + selector6(state); // $ExpectType Ref +})(); diff --git a/types/redux-orm/redux.d.ts b/types/redux-orm/redux.d.ts index a8f1ba6e8c..91aa2bbfac 100644 --- a/types/redux-orm/redux.d.ts +++ b/types/redux-orm/redux.d.ts @@ -1,25 +1,81 @@ import { IndexedModelClasses, ORM, OrmState } from './ORM'; import { OrmSession } from './Session'; -export interface ORMReducer, TAction extends any = any> { - (state: OrmState | undefined, action: TAction): OrmSession; +export interface ORMReducer { + (state: OrmState | undefined, action: TAction): OrmState; } -export type defaultUpdater, TAction extends any = any> = ( +export type defaultUpdater = ( session: OrmSession, action: TAction ) => void; -export function createReducer, TAction extends any = any>( +export function createReducer( orm: ORM, updater?: defaultUpdater ): ORMReducer; -export interface ORMSelector, Result extends any = any> { - (session: OrmSession, ...args: any[]): Result; +export type Selector = (state: S) => R; + +export interface ORMSelector { + (session: OrmSession, ...args: Args): R; } -export function createSelector, Result extends any = any>( +export function createSelector( orm: ORM, - ormSelector: ORMSelector -): (state: OrmState) => Result; + ormStateSelector: Selector>, + selector1: Selector, + selector2: Selector, + selector3: Selector, + selector4: Selector, + selector5: Selector, + selector6: Selector, + ormSelector: ORMSelector +): Selector; + +export function createSelector( + orm: ORM, + ormStateSelector: Selector>, + selector1: Selector, + selector2: Selector, + selector3: Selector, + selector4: Selector, + selector5: Selector, + ormSelector: ORMSelector +): Selector; + +export function createSelector( + orm: ORM, + ormStateSelector: Selector>, + selector1: Selector, + selector2: Selector, + selector3: Selector, + selector4: Selector, + ormSelector: ORMSelector +): Selector; + +export function createSelector( + orm: ORM, + ormStateSelector: Selector>, + selector1: Selector, + selector2: Selector, + selector3: Selector, + ormSelector: ORMSelector +): Selector; + +export function createSelector( + orm: ORM, + ormStateSelector: Selector>, + selector1: Selector, + selector2: Selector, + ormSelector: ORMSelector +): Selector; + +export function createSelector( + orm: ORM, + ormStateSelector: Selector>, + selector1: Selector, + ormSelector: ORMSelector +): Selector; + +export function createSelector(orm: ORM, ormSelector: ORMSelector): Selector, R>;