Skip to content

Commit

Permalink
overhaul typings based on Model declaration merging
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasz-zablocki committed Jul 30, 2019
1 parent 10d51a3 commit 3718dbe
Show file tree
Hide file tree
Showing 13 changed files with 510 additions and 795 deletions.
607 changes: 240 additions & 367 deletions types/redux-orm/Model.d.ts

Large diffs are not rendered by default.

68 changes: 29 additions & 39 deletions types/redux-orm/ORM.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import createDatabase, { Table, Database } from './db';
import { AnyModel } from './Model';
import Model from './Model';
import Session, { OrmSession } from './Session';

/**
Expand All @@ -15,7 +15,7 @@ import Session, { OrmSession } from './Session';
* Internally, this class handles generating a schema specification from models
* to the database.
*/
export class ORM<I extends ORM.IndexedModelClasses<any>, ModelNames extends keyof I = keyof I> {
export class ORM<S extends ORM.Schema, ORegistry extends ORM.Registry<S> = ORM.Registry<S>> {
/**
* Creates a new ORM instance.
*/
Expand All @@ -30,7 +30,7 @@ export class ORM<I extends ORM.IndexedModelClasses<any>, ModelNames extends keyo
*
* @param model - a {@link Model} class to register
*/
register(...model: ReadonlyArray<I[ModelNames]>): void;
register(...model: ReadonlyArray<S[number]>): void;

/**
* Gets a {@link Model} class by its name from the registry.
Expand All @@ -41,40 +41,40 @@ export class ORM<I extends ORM.IndexedModelClasses<any>, ModelNames extends keyo
*
* @return the {@link Model} class, if found
*/
get<K extends ModelNames>(modelName: K): I[K];
get<K extends keyof ORegistry>(modelName: K): ORegistry[K];

/**
* Returns the empty database state.
*
* @see {@link OrmState}
* @see {@link ORM.State}
*
* @return empty state
*/
getEmptyState(): ORM.OrmState<I>;
getEmptyState(): ORM.State<S>;

/**
* Begins an immutable database session.
*
* @see {@link OrmState}
* @see {@link SessionType}
* @see {@link ORM.State}
* @see {@link OrmSession}
*
* @param state - the state the database manages
*
* @return a new {@link Session} instance
* @return a new {@link OrmSession} instance
*/
session(state?: ORM.OrmState<I>): OrmSession<I>;
session(state?: ORM.State<S>): OrmSession<S>;

/**
* Begins an mutable database session.
*
* @see {@link OrmState}
* @see {@link SessionType}
* @see {@link ORM.State}
* @see {@link OrmSession}
*
* @param state - the state the database manages
*
* @return a new {@link Session} instance
* @return a new {@link OrmSession} instance
*/
mutableSession(state: ORM.OrmState<I>): OrmSession<I>;
mutableSession(state: ORM.State<S>): OrmSession<S>;

/**
* Acquire database reference.
Expand All @@ -83,37 +83,27 @@ export class ORM<I extends ORM.IndexedModelClasses<any>, ModelNames extends keyo
*
* @return A {@link Database} instance structured according to registered schema.
*/
getDatabase(): Database<I>;
getDatabase(): Database<S>;
}

export namespace ORM {
/**
* A `{typeof Model[modelName]: typeof Model}` map defining:
*
* - database schema
* - {@link Session} bound Model classes
* - ORM branch state type
*/
type IndexedModelClasses<
T extends { [k in keyof T]: typeof AnyModel } = {},
K extends keyof T = Extract<keyof T, T[keyof T]['modelName']>
> = { [k in K]: T[K] };
/** ORM instantiation opts - enables customization of database creation. */
interface ORMOpts {
createDatabase: typeof createDatabase;
}

/**
* A mapped type capable of inferring ORM branch state type based on schema {@link Model}s.
*/
type OrmState<MClassMap extends IndexedModelClasses<any>> = {
[K in keyof MClassMap]: Table.TableState<MClassMap[K]>
/** Schema definition - an array of {@link Model.Class} constructor functions */
type Schema = ReadonlyArray<Model.Class>;

/** A map of schema {@link Model} classes ({@link Model.Class}, indexed by `{@link Model#modelName}` */
type Registry<S extends Schema> = {
[K in S[number]['modelName']]: Extract<S[number], { modelName: K }>;
};

/**
* ORM instantiation opts.
*
* Enables customization of database creation.
*/
interface ORMOpts {
createDatabase: typeof createDatabase;
}
/** A mapped type capable of inferring ORM branch state type based on schema {@link Model.Class} . */
type State<S extends Schema> = {
[K in keyof Registry<S>]: Table.TableState<Registry<S>[K]>;
};
}

export default ORM;
133 changes: 58 additions & 75 deletions types/redux-orm/QuerySet.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Database } from './db';
import Model, { AnyModel, IdType, ModelClass, Serializable, SessionBoundModel, UpdateProps } from './Model';
import Model from './Model';
import { Serializable } from './helpers';

/**
* <p>
* `QuerySet` class is used to build and make queries to the database
* and operating the resulting set (such as updating attributes
* or deleting the records).
Expand All @@ -15,9 +15,9 @@ import Model, { AnyModel, IdType, ModelClass, Serializable, SessionBoundModel, U
*
* @description The query is executed only when terminating operations are invoked, such as:
*
* - {@link QuerySet#count},
* - {@link QuerySet#toRefArray}
* - {@link QuerySet#at} and other indexed reads
* - {@link QuerySet.count},
* - {@link QuerySet.toRefArray}
* - {@link QuerySet.at} and other indexed reads
*
* After the query is executed, the resulting set is cached in the QuerySet instance.
*
Expand All @@ -29,8 +29,34 @@ import Model, { AnyModel, IdType, ModelClass, Serializable, SessionBoundModel, U
*
* @see {@link QuerySet.QueryBuilder}
*/
export class QuerySet<M extends AnyModel = any, InstanceProps extends object = {}>
export class QuerySet<M extends Model = any, InstanceProps extends Record<string, Serializable> = {}>
implements QuerySet.QueryBuilder<M, InstanceProps> {
/**
* Register custom method on a `QuerySet` class specification.
* QuerySet class may be attached to a {@link Model} class via {@link Model#querySetClass}
*
* @param methodName - name of a method to be available on specific QuerySet class instances
*
* @example:
* class CustomQuerySet extends QuerySet<Book> {
* static currentYear = 2019
* unreleased(): QuerySet<Book> {
* return this.filter(book => book.releaseYear > CustomQuerySet.currentYear);
* }
* }
* CustomQuerySet.addSharedMethod('unreleased');
* // assign specific QuerySet to a Model class
* Book.querySetClass = typeof CustomQuerySet;
* // register models
* const schema = {Book };
* const orm = new ORM<typeof schema>();
* orm.register(Book);
* const session = orm.session(orm.getEmptyState());
* // use shared method
* const unreleased = customQs.unreleased();
*/
static addSharedMethod(methodName: string): void;

/**
* Creates a `QuerySet`. The constructor is mainly for internal use;
* Access QuerySet instances from {@link Model}.
Expand All @@ -39,7 +65,7 @@ export class QuerySet<M extends AnyModel = any, InstanceProps extends object = {
* @param clauses - query clauses needed to evaluate the set.
* @param [opts] - additional options
*/
constructor(modelClass: ModelClass<M>, clauses: Database.QueryClause[], opts?: object);
constructor(modelClass: typeof Model, clauses: Database.QueryClause[], opts?: object);

/**
* Checks if the {@link QuerySet} instance has any records matching the query
Expand All @@ -58,24 +84,17 @@ export class QuerySet<M extends AnyModel = any, InstanceProps extends object = {
toRefArray(): ReadonlyArray<M['ref'] & InstanceProps>;

/**
* Returns an array of {@link SessionBoundModel} instances represented by the QuerySet.
* Returns an array of {@link Model} instances represented by the QuerySet.
*
* @return session bound model instances represented by the QuerySet
*/
toModelArray(): ReadonlyArray<SessionBoundModel<M, InstanceProps>>;

/**
* Returns a string representation of QuerySet instance contents.
*
* @return string representation of QuerySet.
*/
toString(): string;
toModelArray(): ReadonlyArray<M & InstanceProps>;

at(index: number): SessionBoundModel<M, InstanceProps> | undefined;
at(index: number): M & InstanceProps | undefined;

first(): SessionBoundModel<M, InstanceProps> | undefined;
first(): M & InstanceProps | undefined;

last(): SessionBoundModel<M, InstanceProps> | undefined;
last(): M & InstanceProps | undefined;

all(): QuerySet<M, InstanceProps>;

Expand All @@ -90,79 +109,51 @@ export class QuerySet<M extends AnyModel = any, InstanceProps extends object = {

count(): number;

update(mergeObj: UpdateProps<M>): void;
update(mergeObj: Model.UpdateProps<M>): void;

delete(): void;
}

/**
* A {@link QuerySet} extended with {@link ManyToMany} specific functionality.
*/
export interface MutableQuerySet<M extends AnyModel = any, InstanceProps extends object = {}>
/** A {@link QuerySet} extended with {@link ManyToMany} specific functionality. */
export interface MutableQuerySet<M extends Model = any, InstanceProps extends Record<string, Serializable> = {}>
extends QuerySet<M, InstanceProps> {
add: (...entitiesToAdd: Array<IdType<M> | M>) => void;
remove: (...entitiesToRemove: Array<IdType<M> | M>) => void;
add: (...entitiesToAdd: ReadonlyArray<Model.IdType<M> | M>) => void;
remove: (...entitiesToRemove: ReadonlyArray<Model.IdType<M> | M>) => void;
clear: () => void;
}

export namespace QuerySet {
/**
* Register custom method on a `QuerySet` class specification.
* QuerySet class may be attached to a {@link Model} class via {@link Model#querySetClass}
*
* @param methodName - name of a method to be available on specific QuerySet class instances
*
* @example:
* class CustomQuerySet extends QuerySet<Book> {
* static currentYear = 2019
* unreleased(): QuerySet<Book> {
* return this.filter(book => book.releaseYear > CustomQuerySet.currentYear);
* }
* }
* CustomQuerySet.addSharedMethod('unreleased');
* // assign specific QuerySet to a Model class
* Book.querySetClass = typeof CustomQuerySet;
* // register models
* const schema = {Book };
* const orm = new ORM<typeof schema>();
* orm.register(Book);
* const session = orm.session(orm.getEmptyState());
* // use shared method
* const unreleased = customQs.unreleased();
*/
function addSharedMethod(methodName: string): void;

/**
* Interface for building queries in fluent style
*/
interface QueryBuilder<M extends AnyModel = any, InstanceProps extends object = {}> {
interface QueryBuilder<M extends Model = any, InstanceProps extends Record<string, Serializable> = {}> {
/**
* Returns the {@link SessionBoundModel} instance at index `index` in the {@link QuerySet} instance if
* Returns the {@link Model} instance at index `index` in the {@link QuerySet} instance if
* `withRefs` flag is set to `false`, or a reference to the plain JavaScript
* object in the model state if `true`.
*
* @param index - index of the model instance to get
* @return a {@link Model} derived {@link SessionBoundModel} instance at index
* @return a {@link Model} derived {@link Model} instance at index
* `index` in the {@link QuerySet} instance,
* or undefined if the index is out of bounds.
*/
at(index: number): SessionBoundModel<M, InstanceProps> | undefined;
at(index: number): M & InstanceProps | undefined;

/**
* Returns the session bound {@link Model} instance at index 0
* in the {@link QuerySet} instance or undefined if the instance is empty.
*
* @return a {@link Model} derived {@link SessionBoundModel} instance or undefined.
* @return a {@link Model} derived {@link Model} instance or undefined.
*/
first(): SessionBoundModel<M, InstanceProps> | undefined;
first(): M & InstanceProps | undefined;

/**
* Returns the session bound {@link Model} instance at index `QuerySet.count() - 1`
* in the {@link QuerySet} instance or undefined if the instance is empty.
*
* @return a {@link Model} derived {@link SessionBoundModel} instance or undefined.
* @return a {@link Model} derived {@link Model} instance or undefined.
*/
last(): SessionBoundModel<M, InstanceProps> | undefined;
last(): M & InstanceProps | undefined;

/**
* Returns a new {@link QuerySet} instance with the same entities.
Expand Down Expand Up @@ -218,7 +209,7 @@ export namespace QuerySet {
*
* @param mergeObj - an object extending {@link UpdateProps}, to be merged with all the objects in this QuerySet.
*/
update(mergeObj: UpdateProps<M>): void;
update(mergeObj: Model.UpdateProps<M>): void;

/**
* Records a deletion of all the objects in this {@link QuerySet} instance.
Expand All @@ -234,20 +225,20 @@ export namespace QuerySet {
}

/**
* Optional ordering direction.
* Ordering clause.
*
* Either a key of SessionBoundModel or a evaluator function accepting plain object Model representation stored in the database.
*
* {@see QuerySet.orderBy}
*/
type SortOrder = 'asc' | 'desc' | true | false;
type SortIteratee<M extends Model> = keyof M['ref'] | { (row: M['ref']): any };

/**
* Ordering clause.
*
* Either a key of SessionBoundModel or a evaluator function accepting plain object Model representation stored in the database.
* Ordering direction.
*
* {@see QuerySet.orderBy}
*/
type SortIteratee<M extends Model> = keyof M['ref'] | { (row: M['ref']): any };
type SortOrder = 'asc' | 'desc' | true | false;

/**
* Lookup clause as an object specifying props to match with plain object Model representation stored in the database.
Expand All @@ -269,14 +260,6 @@ export namespace QuerySet {
* {@see QuerySet.filter}
*/
type LookupSpec<M extends Model> = LookupProps<M> | LookupPredicate<M>;

/**
* A lookup query result.
*
* May contain additional properties in case {@link LookupProps} clause had been supplied.
* {@see QuerySet.exclude}
* {@see QuerySet.filter}
*/
}

export default QuerySet;
Loading

0 comments on commit 3718dbe

Please sign in to comment.