[@types/redux-orm] typings for redux-orm@0.13.3 (#35609)

This commit is contained in:
tomasz-zablocki 2019-06-05 23:46:27 +02:00 committed by Andrew Casey
parent 806e23a198
commit ca0f882668
14 changed files with 1866 additions and 276 deletions

545
types/redux-orm/Model.d.ts vendored Normal file
View File

@ -0,0 +1,545 @@
import { ModelTableOpts, TableOpts } from './db';
import { IdAttribute } from './db/Table';
import { AttributeWithDefault, FieldSpecMap, ForeignKey, ManyToMany, OneToOne } from './fields';
import { Optional, OptionalKeys, Overwrite, PickByValue } from './helpers';
import { IdOrModelLike, ModelField } from './index';
import QuerySet, { LookupSpec, MutableQuerySet, SortIteratee, SortOrder } from './QuerySet';
import { OrmSession } from './Session';
/**
* A primitive value
*/
export type Primitive = number | string | boolean;
/**
* Serializable value: a primitive, undefined, a serializable object or an array of those
*/
export type Serializable =
| Primitive
| Primitive[]
| undefined
| {
[K: string]: Serializable | Serializable[];
};
/**
* Object restricted to serializable properties only
*/
export interface SerializableMap {
[K: string]: Serializable | Serializable[];
}
/**
* A union of supported model field types
*
* Specify foreign key and one-to-one association properties as Model typed properties.
*
* Specify many-to-many and reverse-fk associations as related Model's specification of:
* - {@link MutableQuerySet} - for many-to-many relations
* - {@link QuerySet} - for reverse side of foreign keys
*/
export type ModelField = MutableQuerySet | QuerySet | SessionBoundModel | Serializable;
/**
* Map of fields restriction to supported field types.
*/
export interface ModelFieldMap {
[K: string]: ModelField;
}
/**
* A Model-derived mapped type for supplying relations and alike.
*
* Either a primitive type matching Model's identifier type or a map containing an {IdAttribute: IdType} pair,
* where IdAttribute and IdType match respective Model property key and type
*/
export type IdOrModelLike<M extends Model> = IdType<M> | IdEntry<M>;
/**
* The heart of an ORM, the data model.
*
* The fields you specify to the Model will be used to generate
* a schema to the database, related property accessors, and
* possibly through models.
*
* In each {@link Session} you instantiate from an {@link ORM} instance,
* you will receive a session-specific subclass of this Model. The methods
* you define here will be available to you in sessions.
*
* An instance of {@link Model} represents a record in the database, though
* it is possible to generate multiple instances from the same record in the database.
*
* To create data models in your schema, subclass {@link Model}. To define
* information about the data model, override static class methods. Define instance
* 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> {
/**
* A string constant identifying specific Model, necessary to retain the shape of state and relations through transpilation steps
*/
static modelName: string;
/**
* Model field descriptors.
* @see {@link Attribute}
* @see {@link OneToOne}
* @see {@link ForeignKey}
* @see {@link ManyToMany}
*/
static fields: FieldSpecMap;
/**
* Returns the options object passed to the database for the table that represents
* this Model class.
*
* Returns an empty object by default, which means the database
* will use default options. You can either override this function to return the options
* you want to use, or assign the options object as a static property of the same name to the
* Model class.
*
* @return the options object passed to the database for the table
* representing this Model class.
*/
static options: { (): TableOpts } | TableOpts;
/**
* The key of Model's identifier property
*/
static readonly idAttribute: string;
/**
* {@link QuerySet} class associated with this Model class.
*
* Defaults to base {@link QuerySet}
*/
static querySetClass: typeof QuerySet;
/**
* @see {@link Model.getQuerySet}
*/
static readonly query: QuerySet;
/**
* Returns a reference to the plain JS object in the store.
* Make sure to not mutate this.
*
* @return a reference to the plain JS object in the store
*/
readonly ref: Ref<InstanceType<MClass>>;
/**
* Creates a Model instance from it's properties.
* Don't use this to create a new record; Use the static method {@link Model#create}.
* @param props - the properties to instantiate with
*/
constructor(props: Fields);
/**
* Model specific reducer function.
*
* An alternative to standalone reducer function.
*
* @see {@link createReducer}
*
* @param action - store-dispatched action instance
* @param modelType - a {@link ModelType} parametrized with a
* {@link Model} type that the reducer is being attached to.
* @param session - an optional parameter, can be used for querying other Models (mutations are not supported)
*/
static reducer(action: any, modelType: ModelType<any>, session: OrmSession<any>): void;
/**
* Creates a new record in the database, instantiates a {@link Model} and returns it.
*
* If you pass values for many-to-many fields, instances are created on the through
* model as well.
*
* @param userProps - the new {@link Model}'s properties.
* @return a new {@link SessionBoundModel} instance.
*/
static create<M extends AnyModel, TProps extends CreateProps<M>>(userProps: TProps): SessionBoundModel<M, TProps>;
/**
* Creates a new or update existing record in the database, instantiates a {@link Model} and returns it.
*
* If you pass values for many-to-many fields, instances are created on the through
* model as well.
*
* @param userProps - the upserted {@link Model}'s properties.
* @return a {@link SessionBoundModel} instance.
*/
static upsert<M extends AnyModel, TProps extends UpsertProps<M>>(userProps: TProps): SessionBoundModel<M, TProps>;
/**
* Gets the {@link Model} instance that matches properties in `lookupObj`.
* Throws an error if {@link Model} if multiple records match
* the properties.
*
* @param lookupObj - the properties used to match a single entity.
* @throws {Error} If more than one entity matches the properties in `lookupObj`.
* @return a {@link SessionBoundModel} instance that matches the properties in `lookupObj`.
*/
static get<M extends AnyModel, TProps extends LookupSpec<M>>(
lookupObj: TProps
): SessionBoundModel<M, TProps> | null;
/**
* Returns a {@link Model} instance for the object with id `id`.
* Returns `null` if the model has no instance with id `id`.
*
* You can use {@link Model#idExists} to check for existence instead.
*
* @param id - the `id` of the object to get
* @return a {@link SessionBoundModel} instance with id `id`
*/
static withId<M extends AnyModel>(id: IdType<M>): SessionBoundModel<M> | null;
/**
* Returns a boolean indicating if an entity
* with the id `id` exists in the state.
*
* @param id - a value corresponding to the id attribute of the {@link Model} class.
* @return a boolean indicating if entity with `id` exists in the state
*
* @since 0.11.0
*/
static idExists(id: string | number): boolean;
/**
* @return A string representation of this {@link Model} class.
*/
static toString(): string;
/**
* Manually mark individual instances as accessed.
* This allows invalidating selector memoization within mutable sessions.
*
* @param ids - Array of primary key values
*/
static markAccessed(ids: Array<string | number>): void;
/**
* Manually mark this model's table as scanned.
* This allows invalidating selector memoization within mutable sessions.
*
*/
static markFullTableScanned(): void;
/**
* Returns an instance of the model's `querySetClass` field.
* By default, this will be an empty {@link QuerySet}.
*
* @return An instance of the model's `querySetClass`.
*/
static getQuerySet(): QuerySet;
/**
* @see {@link QuerySet.all}
*/
static all<M extends AnyModel>(this: ModelType<M>): QuerySet<M>;
/**
* @see {@link QuerySet.at}
*/
static at(index: number): SessionBoundModel | undefined;
/**
* @see {@link QuerySet.first}
*/
static first(): SessionBoundModel | undefined;
/**
* @see {@link QuerySet.last}
*/
static last(): SessionBoundModel | undefined;
/**
* @see {@link QuerySet.update}
*/
static update(props: UpdateProps<Model>): void;
/**
* @see {@link QuerySet.filter}
*/
static filter(props: LookupSpec<Model>): QuerySet;
/**
* @see {@link QuerySet.exclude}
*/
static exclude(props: LookupSpec<Model>): QuerySet;
/**
* @see {@link QuerySet.orderBy}
*/
static orderBy(iteratees: ReadonlyArray<SortIteratee<Model>>, orders?: ReadonlyArray<SortOrder>): QuerySet;
/**
* @see {@link QuerySet.count}
*/
static count(): number;
/**
* @see {@link QuerySet.exists}
*/
static exists(): boolean;
/**
* @see {@link QuerySet.delete}
*/
static delete(): void;
/**
* Gets the {@link Model} class or subclass constructor (the class that
* instantiated this instance).
*
* @return The {@link Model} class or subclass constructor used to instantiate
* this instance.
*/
getClass(): MClass;
/**
* Gets the id value of the current instance by looking up the id attribute.
* @return The id value of the current instance.
*/
getId(): string | number;
/**
* @return A string representation of this {@link Model} instance.
*/
toString(): string;
/**
* Returns a boolean indicating if `otherModel` equals this {@link Model} instance.
* Equality is determined by shallow comparing their attributes.
*
* This equality is used when you call {@link Model#update}.
* You can prevent model updates by returning `true` here.
* However, a model will always be updated if its relationships are changed.
*
* @param otherModel - a {@link Model} instance to compare
* @return a boolean indicating if the {@link Model} instance's are equal.
*/
equals(otherModel: Model | SessionBoundModel): boolean;
/**
* Updates a property name to given value for this {@link Model} instance.
* The values are immediately committed to the database.
*
* @param propertyName - name of the property to set
* @param value - value assigned to the property
*/
set<K extends string>(propertyName: K, value: RefPropOrSimple<InstanceType<MClass>, K>): void;
/**
* Assigns multiple fields and corresponding values to this {@link Model} instance.
* The updates are immediately committed to the database.
*
* @param userMergeObj - an object that will be merged with this instance.
*/
update(userMergeObj: UpdateProps<InstanceType<MClass>>): void;
/**
* Updates {@link Model} instance attributes to reflect the
* database state in the current session.
*/
refreshFromState(): void;
/**
* Deletes the record for this {@link Model} instance.
* Fields and values on the instance are still accessible after the call.
*/
delete(): void;
}
/**
* Model wildcard type.
*/
export class AnyModel extends Model {}
/**
* {@link Model#upsert} argument type
*
* Relations can be provided in a flexible manner for both many-to-many and foreign key associations
* @see {@link IdOrModelLike}
*/
export type UpsertProps<M extends Model> = Overwrite<Partial<CreateProps<M>>, Required<IdEntry<M>>>;
/**
* {@link Model#update} argument type
*
* All properties are optional.
* Supplied properties are type-checked against the type of related Model's fields.
* Relations can be provided in a flexible manner for both many-to-many and foreign key associations
* @see {@link IdOrModelLike}
*/
export type UpdateProps<M extends Model> = Omit<UpsertProps<M>, IdKey<M>>;
/**
* @internal
*/
export type CustomInstanceProps<M extends AnyModel, Props extends object> = PickByValue<
Omit<Props, Extract<keyof Props, keyof ModelFields<M>>>,
Serializable
>;
/**
* Model id property key extraction helper.
*
* Falls back to `'id'` if not specified explicitly via {@link Model.options}.
*/
export type IdKey<M extends AnyModel> = IdAttribute<ModelClass<M>>;
/**
* Model id property type extraction helper.
*
* Falls back to `number` if not specified explicitly via {@link Model.options}.
*/
export type IdType<M extends Model> = IdKey<M> extends infer U
? U extends keyof ModelFields<M>
? ModelFields<M>[U] extends string | number
? ModelFields<M>[U]
: never
: number
: number;
/**
* A single entry map representing IdKey: IdType property of supplied {@link Model}.
*/
export type IdEntry<M extends Model> = { [K in IdKey<M>]: IdType<M> };
/**
* Type of {@link Model.ref} / database entry for a particular Model type
*/
export type Ref<M extends Model> = {
[K in keyof RefFields<M>]: ModelFields<M>[K] extends AnyModel ? IdType<ModelFields<M>[K]> : RefFields<M>[K]
};
/**
* A mapped type restricting allowed types of second {@link Model.set} argument.
* Depending on the first argument `propertyName` argument, value type can be restricted to:
* - 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>
? Ref<M>[K]
: Serializable;
/**
* A Model-derived mapped type, representing model instance bound to a session.
*
* SessionBoundModels relation properties for convenient association traversal.
* Custom type-checked properties are available on `SessionBoundModel` instances created using
* @link Model#create} or {@link Model#upsert} calls.
*/
export type SessionBoundModel<M extends Model = any, InstanceProps extends object = {}> = M &
{ [K in keyof ModelFields<M>]: SessionBoundModelField<M, K> } &
CustomInstanceProps<M, InstanceProps>;
/**
* Static side of a particular {@link Model} with member signatures narrowed to provided {@link Model} type
*
* @template M a model type narrowing static {@link Model} member signatures.
*
* @inheritDoc
*/
export interface ModelType<M extends AnyModel> extends QuerySet<M> {
new (props: ModelFields<M>): SessionBoundModel<M>;
options: ModelTableOpts<ModelClass<M>>;
modelName: ModelClass<M>['modelName'];
fields: ModelClass<M>['fields'];
/**
* @see {@link Model#idExists}
*/
idExists(id: IdType<M>): boolean;
/**
* @see {@link Model#withId}
*/
withId(id: IdType<M>): SessionBoundModel<M> | null;
/**
* @see {@link Model#get}
*/
get<TLookup extends LookupSpec<M>>(lookupSpec: TLookup): SessionBoundModel<M, TLookup> | null;
/**
* @see {@link Model#create}
*/
create<TProps extends CreateProps<M>>(props: TProps): SessionBoundModel<M, TProps>;
/**
* @see {@link Model#upsert}
*/
upsert<TProps extends UpsertProps<M>>(props: TProps): SessionBoundModel<M, TProps>;
}
/**
* @internal
*/
export type ModelClass<M extends AnyModel> = ReturnType<M['getClass']>;
/**
* @internal
*/
export type ModelFields<M extends Model> = [ConstructorParameters<ModelClass<M>>] extends [[infer U]]
? U extends ModelFieldMap
? U
: never
: never;
/**
* @internal
*/
export type FieldSpecKeys<M extends AnyModel, TField> = keyof PickByValue<ModelClass<M>['fields'], TField>;
/**
* @internal
*/
export type RefFields<M extends AnyModel, K extends keyof ModelFields<M> = keyof ModelFields<M>> = Omit<
ModelFields<M>,
Extract<K, FieldSpecKeys<M, ManyToMany>>
>;
/**
* @internal
*/
export type SessionBoundModelField<M extends AnyModel, K extends keyof ModelFields<M>> = ModelFields<
M
>[K] extends AnyModel
? SessionBoundModel<ModelFields<M>[K]>
: ModelFields<M>[K];
/**
* {@link Model#create} argument type
*
* Relations can be provided in a flexible manner for both many-to-many and foreign key associations
* @see {@link IdOrModelLike}
*/
export type CreateProps<
M extends AnyModel,
RFields extends Required<ModelFields<M>> = Required<ModelFields<M>>
> = Optional<
{
[K in keyof ModelFields<M>]: {
[P in K]: RFields[P] extends MutableQuerySet<infer RM>
? ReadonlyArray<IdOrModelLike<RM>>
: (RFields[P] extends QuerySet
? never
: RFields[P] extends AnyModel
? (P extends FieldSpecKeys<M, OneToOne | ForeignKey> ? IdOrModelLike<RFields[P]> : never)
: RFields[P])
}[K]
},
OptionalCreatePropsKeys<M>
>;
/**
* @internal
*/
export type OptionalCreatePropsKeys<M extends Model> = IdType<M> extends number
? (IdKey<M> | OptionalKeys<ModelFields<M>> | FieldSpecKeys<M, AttributeWithDefault>)
: (OptionalKeys<ModelFields<M>> | FieldSpecKeys<M, AttributeWithDefault>);

115
types/redux-orm/ORM.d.ts vendored Normal file
View File

@ -0,0 +1,115 @@
import { Database, DatabaseCreator, TableState } from './db';
import { AnyModel } from './Model';
import Session, { OrmSession } from './Session';
/**
* A `{typeof Model[modelName]: typeof Model}` map defining:
*
* - database schema
* - {@link Session} bound Model classes
* - ORM branch state type
*/
export 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] };
/**
* A mapped type capable of inferring ORM branch state type based on schema {@link Model}s.
*/
export type OrmState<MClassMap extends IndexedModelClasses<any>> = { [K in keyof MClassMap]: TableState<MClassMap[K]> };
/**
* ORM instantiation opts.
*
* Enables customization of database creation.
*/
export interface ORMOpts {
createDatabase: DatabaseCreator;
}
/**
* ORM - the Object Relational Mapper.
*
* Use instances of this class to:
*
* - Register your {@link Model} classes using {@link ORM#register}
* - Get the empty state for the underlying database with {@link ORM#getEmptyState}
* - Start an immutable database session with {@link ORM#session}
* - Start a mutating database session with {@link ORM#mutableSession}
*
* Internally, this class handles generating a schema specification from models
* to the database.
*/
export class ORM<I extends IndexedModelClasses<any>, ModelNames extends keyof I = keyof I> {
/**
* Creates a new ORM instance.
*/
constructor(opts?: ORMOpts);
/**
* Registers a {@link Model} class to the ORM.
*
* If the model has declared any ManyToMany fields, their
* through models will be generated and registered with
* this call, unless a custom through model has been specified.
*
* @param model - a {@link Model} class to register
*/
register(...model: ReadonlyArray<I[ModelNames]>): void;
/**
* Gets a {@link Model} class by its name from the registry.
*
* @param modelName - the name of the {@link Model} class to get
*
* @throws If {@link Model} class is not found.
*
* @return the {@link Model} class, if found
*/
get<K extends ModelNames>(modelName: K): I[K];
/**
* Returns the empty database state.
*
* @see {@link OrmState}
*
* @return empty state
*/
getEmptyState(): OrmState<I>;
/**
* Begins an immutable database session.
*
* @see {@link OrmState}
* @see {@link SessionType}
*
* @param state - the state the database manages
*
* @return a new {@link Session} instance
*/
session(state: OrmState<I>): OrmSession<I>;
/**
* Begins an mutable database session.
*
* @see {@link OrmState}
* @see {@link SessionType}
*
* @param state - the state the database manages
*
* @return a new {@link Session} instance
*/
mutableSession(state: OrmState<I>): OrmSession<I>;
/**
* Acquire database reference.
*
* If no database exists, an instance is created using either default or supplied implementation of {@link DatabaseCreator}.
*
* @return A {@link Database} instance structured according to registered schema.
*/
getDatabase(): Database<I>;
}
export default ORM;

264
types/redux-orm/QuerySet.d.ts vendored Normal file
View File

@ -0,0 +1,264 @@
import { QueryClause } from './db';
import Model, {
AnyModel,
CustomInstanceProps,
IdOrModelLike,
ModelClass,
Ref,
SessionBoundModel,
UpdateProps
} from './Model';
/**
* Optional ordering direction.
*
* {@see QuerySet.orderBy}
*/
export type SortOrder = 'asc' | 'desc' | true | false;
/**
* Ordering clause.
*
* Either a key of SessionBoundModel or a evaluator function accepting plain object Model representation stored in the database.
*
* {@see QuerySet.orderBy}
*/
export type SortIteratee<M extends Model> = keyof Ref<M> | { (row: Ref<M>): any };
/**
* Lookup clause as an object specifying props to match with plain object Model representation stored in the database.
* {@see QuerySet.exclude}
* {@see QuerySet.filter}
*/
export type LookupProps<M extends Model> = Partial<Ref<M>>;
/**
* Lookup clause as predicate accepting plain object Model representation stored in the database.
* {@see QuerySet.exclude}
* {@see QuerySet.filter}
*/
export type LookupPredicate<M extends Model> = (row: Ref<M>) => boolean;
/**
* A union of lookup clauses.
* {@see QuerySet.exclude}
* {@see QuerySet.filter}
*/
export 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 type LookupResult<M extends Model, TLookup extends LookupSpec<M>> = TLookup extends LookupPredicate<M>
? QuerySet<M>
: QuerySet<M, CustomInstanceProps<M, LookupProps<M>>>;
/**
* <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).
* <p>
*
* @example queries are built lazily
* const qs = Book.all()
* .filter(book => book.releaseYear > 1999)
* .orderBy('name')
*
* @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
*
* After the query is executed, the resulting set is cached in the QuerySet instance.
*
* QuerySet instances return copies, so chaining filters doesn't
* mutate the previous instances.
*
* @template M type of {@link Model} instances returned by QuerySet's methods.
* @template InstanceProps additional properties available on QuerySet's elements.
*/
export default class QuerySet<M extends AnyModel = any, InstanceProps extends object = {}> {
/**
* Creates a `QuerySet`. The constructor is mainly for internal use;
* Access QuerySet instances from {@link Model}.
*
* @param modelClass - the {@link Model} class of objects in this QuerySet.
* @param clauses - query clauses needed to evaluate the set.
* @param [opts] - additional options
*/
constructor(modelClass: ModelClass<M>, clauses: QueryClause[], opts?: object);
/**
* 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;
/**
* Returns a new {@link QuerySet} instance with the same entities.
* @return a new QuerySet with the same entities.
*/
all(): QuerySet<M, InstanceProps>;
/**
* Returns the {@link SessionBoundModel} 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
* `index` in the {@link QuerySet} instance,
* or undefined if the index is out of bounds.
*/
at(index: number): SessionBoundModel<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.
*/
first(): SessionBoundModel<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.
*/
last(): SessionBoundModel<M, InstanceProps> | undefined;
/**
* Returns a new {@link QuerySet} instance with entities that match properties in `lookupObj`.
*
* @param lookupObj - the properties to match objects with ({@link LookupProps}).
* Can also be a function ({@link LookupPredicate}).
*
* @return a new {@link QuerySet} instance with objects that passed the filter.
*/
filter<TLookup extends LookupSpec<M>>(lookupObj: TLookup): LookupResult<M, TLookup>;
/**
* Returns a new {@link QuerySet} instance with entities that do not match properties in `lookupObj`.
*
* @param lookupObj - the properties to match objects with ({@link LookupProps}).
* Can also be a function ({@link LookupPredicate}).
*
* @return a new {@link QuerySet} instance with objects that passed the filter.
*/
exclude<TLookup extends LookupSpec<M>>(lookupObj: TLookup): LookupResult<M, TLookup>;
/**
* Returns a new {@link QuerySet} instance with entities ordered by `iteratees` in ascending
* order, unless otherwise specified. Delegates to `lodash.orderBy`.
*
* @param iteratees - an array or a single {@link SortIteratee} where each item can be a string or a
* function. If a string is supplied, it should
* correspond to property on the entity that will
* determine the order. If a function is supplied,
* it should return the value to order by.
*
* @param [orders] - the sort orders of `iteratees`. If unspecified, all iteratees
* will be sorted in ascending order. `true` and `'asc'`
* correspond to ascending order, and `false` and `'desc`
* to descending order. Accepts an array or a single {@link SortOrder}.
*
* @return a new {@link QuerySet} with objects ordered by `iteratees`.
*/
orderBy(
iteratees: SortIteratee<M> | ReadonlyArray<SortIteratee<M>>,
orders?: SortOrder | ReadonlyArray<SortOrder>
): QuerySet<M>;
/**
* Returns the number of {@link Model} instances represented by the QuerySet.
*
* @return length of the QuerySet
*/
count(): number;
/**
* Checks if the {@link QuerySet} instance has any records matching the query
* in the database.
*
* @return `true` if the {@link QuerySet} instance contains entities, else `false`.
*/
exists(): boolean;
/**
* Returns an array of the plain objects represented by the QuerySet.
* The plain objects are direct references to the store.
*
* @return references to the plain JS objects represented by the QuerySet
*/
toRefArray(): ReadonlyArray<Ref<M>>;
/**
* Returns an array of {@link SessionBoundModel} instances represented by the QuerySet.
*
* @return session bound model instances represented by the QuerySet
*/
toModelArray(): ReadonlyArray<SessionBoundModel<M, InstanceProps>>;
/**
* Records an update specified with `mergeObj` to all the objects
* in the {@link QuerySet} instance.
*
* @param mergeObj - an object extending {@link UpdateProps}, to be merged with all the objects in this QuerySet.
*/
update(mergeObj: UpdateProps<M>): void;
/**
* Records a deletion of all the objects in this {@link QuerySet} instance.
*/
delete(): void;
/**
* Returns a string representation of QuerySet instance contents.
*
* @return string representation of QuerySet.
*/
toString(): string;
}
/**
* {@link QuerySet} extensions available on {@link ManyToMany} fields of session bound {@link Model} instances.
*/
export interface ManyToManyExtensions<M extends AnyModel> {
add: (...entitiesToAdd: ReadonlyArray<IdOrModelLike<M>>) => void;
remove: (...entitiesToRemove: ReadonlyArray<IdOrModelLike<M>>) => void;
clear: () => void;
}
/**
* A {@link QuerySet} extended with {@link ManyToMany} specific functionality - {@link ManyToManyExtensions}.
*/
export interface MutableQuerySet<M extends AnyModel = any, InstanceProps extends object = {}>
extends ManyToManyExtensions<M>,
QuerySet<M, InstanceProps> {}

72
types/redux-orm/Session.d.ts vendored Normal file
View File

@ -0,0 +1,72 @@
import { IndexedModelClasses, ORM, OrmState } from './ORM';
import { Database, QueryResult, QuerySpec, UpdateSpec } from './db';
import { Assign } from './helpers';
import { ModelType } from './Model';
export type BatchToken = any;
export default class Session<I extends IndexedModelClasses<any>> {
/**
* list of bound {@link Model} classes bound to this session, bootstrapped during {@link @ORM.register}.
*
* @see {@link ModelType}
*/
readonly sessionBoundModels: ReadonlyArray<ModelType<InstanceType<I[keyof I]>>>;
/**
* Current {@link OrmState}, specific to registered schema
*/
readonly state: OrmState<I>;
/**
* Creates a new Session.
*
* @param schema - {@Link ORM} instance, with bootstrapped {@link Model} prototypes.
* @param db - a {@link Database} instance
* @param state - the database {@link OrmState}
* @param withMutations? - whether the session should mutate data
* @param batchToken? - a {@link BatchToken} used by the backend to identify objects that can be
* mutated. If none is provided a default of `Symbol('ownerId')` will be created.
*
*/
constructor(schema: ORM<I>, db: Database<I>, state: OrmState<I>, withMutations?: boolean, batchToken?: BatchToken);
/**
* Executes query against model state.
*
* @param query - the query command object.
*
* @returns query result.
*
* @see {@link QueryType}
* @see {@link QueryClause}
* @see {@link QuerySpec}
* @see {@link QueryResult}
*/
query(query: QuerySpec): QueryResult;
/**
* Applies update to a model state.
*
* @param update - the update command object.
*
* @returns query result.
*
* @see {@link DbAction}
* @see {@link UpdateSpec}
* @see {@link DbActionResult}
* @see {@link UpdateResult}
*/
applyUpdate<P>(update: UpdateSpec<P>): P;
}
/**
* An {@link ORM}-bound {@link Session} instance, extended with a set of {@link ModelType} properties.
*
* Extension is a map of {@link ModelType} accessible under keys within a set of {@link Model#modelName} values
* for registered {@link Model} classes.
*/
export type OrmSession<I extends IndexedModelClasses<any>> = Assign<
Session<I>,
{ [K in keyof I]: ModelType<InstanceType<I[K]>> }
>;

10
types/redux-orm/constants.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
export const UPDATE = 'REDUX_ORM_UPDATE';
export const DELETE = 'REDUX_ORM_DELETE';
export const CREATE = 'REDUX_ORM_CREATE';
export const FILTER = 'REDUX_ORM_FILTER';
export const EXCLUDE = 'REDUX_ORM_EXCLUDE';
export const ORDER_BY = 'REDUX_ORM_ORDER_BY';
export const SUCCESS = 'SUCCESS';
export const FAILURE = 'FAILURE';

160
types/redux-orm/db/Database.d.ts vendored Normal file
View File

@ -0,0 +1,160 @@
import { IndexedModelClasses, OrmState } from '../ORM';
import { CREATE, DELETE, EXCLUDE, FAILURE, FILTER, ORDER_BY, SUCCESS, UPDATE } from '../constants';
import { ModelTableOpts, Table } from './Table';
import { SerializableMap } from '../Model';
import { BatchToken } from '../Session';
/**
* A type of {@link QueryClause}.
*/
export type QueryType = typeof FILTER | typeof EXCLUDE | typeof ORDER_BY;
/**
* A single `QueryClause`.
* Multiple `QueryClause`s can be combined into a {@link Query}.
*/
export interface QueryClause<Payload extends object = {}> {
type: QueryType;
payload: Payload;
}
/**
* Query definition, contains target table and a collection of {@link QueryClause}.
*/
export interface Query {
table: string;
clauses: QueryClause[];
}
/**
* Query wrapper definition, wraps {@link Query}.
*/
export interface QuerySpec {
query: Query;
}
/**
* Query result.
*/
export interface QueryResult<Row extends SerializableMap = {}> {
rows: ReadonlyArray<Row>;
}
/**
* A type of data update to perform.
*/
export type UpdateType = typeof CREATE | typeof UPDATE | typeof DELETE;
/**
* A status of data update operation.
*/
export type UpdateStatus = typeof SUCCESS | typeof FAILURE;
/**
* Data update definition
*/
export interface UpdateSpec<Payload = any> {
action: UpdateType;
payload?: Payload;
query?: Query;
}
/**
* Data update result.
*/
export interface UpdateResult<I extends IndexedModelClasses<any>, Payload extends object = {}> {
status: UpdateStatus;
state: OrmState<I>;
payload: Payload;
}
/**
* Transactions aggregate batches of operations.
*/
export interface Transaction {
batchToken: BatchToken;
withMutations: boolean;
}
/**
* Schema specification, required for default database creator.
*
* @see {@link DatabaseCreator}
* @see {@link ModelTableOpts}
* @see {@link Table}
*/
export interface SchemaSpec<I extends IndexedModelClasses<any>> {
tables: { [K in keyof I]: ModelTableOpts<I[K]> };
}
/**
* A Database parametrized by schema made of {@link Model} classes.
*
* @see {@link SchemaSpec}
* @see {@link TableSpec}
* @see {@link Table}
*/
export interface Database<I extends IndexedModelClasses<any>, Tables = { [K in keyof I]: Table<I[K]> }> {
/**
* Returns the empty database state.
*
* @see {@link OrmState}
*
* @return empty state
*/
getEmptyState(): OrmState<I>;
/**
* Execute a query against a given state.
*
* @param querySpec - a query definition.
* @param state - the state to query against.
*
* @see {@link QuerySpec}
* @see {@link OrmState}
* @see {@link OrmState}
*
* @return a {@link QueryResult} containing 0 to many {@link QueryResult.rows}.
*/
query(querySpec: QuerySpec, state: OrmState<I>): QueryResult;
/**
* Apply an update to a given state.
*
* @param updateSpec - a data update definition.
* @param tx - a transaction for batches of operations.
* @param state - the state to apply update to.
*
* @see {@link UpdateSpec}
* @see {@link Transaction}
* @see {@link OrmState}
*
* @return a {@link UpdateResult} containing 0 to many {@link QueryResult.rows}.
*/
update(updateSpec: UpdateSpec, tx: Transaction, state: OrmState<I>): UpdateResult<I>;
/**
* Return a {@link Table} structure based on provided table name.
* @param tableName - the name of the {@link Table} to describe
*
* @return a {@link Table} instance matching given `tableName` or `undefined` if no such table exists.
*/
describe<K extends keyof Tables>(tableName: K): Tables[K];
}
/**
* Database creation function type.
*/
export type DatabaseCreator = typeof createDatabase;
/**
* Default database creation procedure handle.
*
* @param schemaSpec - a {@link SchemaSpec} to built the {@link Database} from.
*
* @return a {@Link Database} instance, ready for query and data update operation.
*/
export function createDatabase<I extends IndexedModelClasses<any>>(schemaSpec: SchemaSpec<I>): Database<I>;
export default createDatabase;

126
types/redux-orm/db/Table.d.ts vendored Normal file
View File

@ -0,0 +1,126 @@
import Model, { AnyModel, FieldSpecKeys, IdType, Ref } from '../Model';
import { ForeignKey, OneToOne, TableOpts } from '../index';
import { Field } from '../fields';
/**
* {@link TableOpts} used for {@link Table} customization.
*
* Supplied via {@link Model#options}.
*
* If no customizations were provided, the table uses following default options:
* <br/>
* ```typescript
* {
* idAttribute: 'id',
* arrName: 'items',
* mapName: 'itemsById'
* }
* ```
* <br/>
* @see {@link Model}
* @see {@link Model#options}
* @see {@link OrmState}
*/
export interface TableOpts {
readonly idAttribute?: string;
readonly arrName?: string;
readonly mapName?: string;
readonly fields?: { [K: string]: Field };
}
/**
* @internal
*/
export type ExtractModelOption<
MClass extends typeof AnyModel,
K extends keyof TableOpts,
DefaultValue extends string
> = MClass['options'] extends () => { [P in K]: infer R }
? R extends string
? R
: DefaultValue
: MClass['options'] extends { [P in K]: infer R }
? R extends string
? R
: DefaultValue
: DefaultValue;
/**
* Model idAttribute option extraction helper.
*
* Falls back to `'id'` if not specified explicitly via {@link Model.options}.
*/
export type IdAttribute<MClass extends typeof AnyModel> = ExtractModelOption<MClass, 'idAttribute', 'id'>;
/**
* Model arrName option extraction helper.
*
* Falls back to `'items'` if not specified explicitly via {@link Model.options}.
*/
export type ArrName<MClass extends typeof AnyModel> = ExtractModelOption<MClass, 'arrName', 'items'>;
/**
* Model mapName option extraction helper.
*
* Falls back to `'itemsById'` if not specified explicitly via {@link Model.options}.
*/
export type MapName<MClass extends typeof AnyModel> = ExtractModelOption<MClass, 'mapName', 'itemsById'>;
/**
* Unbox {@link Model#options} or fallback to default for others.
*
* @internal
*/
export interface ModelTableOpts<MClass extends typeof AnyModel> {
readonly idAttribute: IdAttribute<MClass>;
readonly arrName: ArrName<MClass>;
readonly mapName: MapName<MClass>;
readonly fields: MClass['fields'];
}
/**
* Handles the underlying data structure for a {@link Model} class.
*/
export class Table<MClass extends typeof AnyModel> {
/**
* Creates a new {@link Table} instance.
*
* @param userOpts - options to use.
* @param [userOpts.idAttribute=DefaultTableOpts.idAttribute] - the id attribute of the entity.
* @param [userOpts.arrName=DefaultTableOpts.arrName] - the state attribute where an array of
* entity id's are stored
* @param [userOpts.mapName=DefaultTableOpts.mapName] - the state attribute where the entity objects
* are stored in a id to entity object
* map.
* @param [userOpts.fields=DefaultTableOpts.fields] - mapping of field key to {@link Field} object
*/
constructor(userOpts?: ModelTableOpts<MClass>);
getEmptyState(): TableState<MClass>;
}
/**
* Type of {@link Model} state's branch `meta` field.
*/
export interface DefaultMeta<MIdType> {
maxId: MIdType extends number ? number : null | number;
}
export type TableIndexes<MClass extends typeof AnyModel> = {
[K in FieldSpecKeys<InstanceType<MClass>, OneToOne | ForeignKey>]: string
};
/**
* A mapped type parametrized by specific {@link Model} class.
*
* Infers actual state of the ORM branch based on the {@link Model} class provided.
*/
export type TableState<MClass extends typeof AnyModel> = {
readonly meta: DefaultMeta<IdType<InstanceType<MClass>>>;
readonly indexes: TableIndexes<MClass>;
} & { readonly [K in ArrName<MClass>]: ReadonlyArray<IdType<InstanceType<MClass>>> } &
{
readonly [K in MapName<MClass>]: {
readonly [K: string]: Ref<InstanceType<MClass>>;
}
};

5
types/redux-orm/db/index.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import createDatabase = require('./Database');
export * from './Database';
export { ModelTableOpts, TableOpts, TableState, Table } from './Table';
export default createDatabase;

73
types/redux-orm/fields.d.ts vendored Normal file
View File

@ -0,0 +1,73 @@
export class Field {
readonly index: boolean;
}
export interface AttributeOpts {
getDefault?: () => any;
}
export class Attribute extends Field {
constructor(opts?: AttributeOpts);
}
export interface AttributeWithDefault extends Attribute {
getDefault(): any;
}
export interface RelationalFieldOpts {
to: string;
relatedName?: string;
through?: string;
throughFields?: {
to: string;
from: string;
};
as?: string;
}
export class RelationalField extends Field {
constructor(toModelName: string, relatedName?: string);
constructor(opts: RelationalFieldOpts);
}
export class OneToOne extends RelationalField {}
export class ForeignKey extends RelationalField {
readonly index: true;
}
export class ManyToMany extends RelationalField {
readonly index: false;
}
export interface AttrCreator {
(): Attribute;
(opts: AttributeOpts): AttributeWithDefault;
}
export interface FkCreator {
(toModelName: string, relatedName?: string): ForeignKey;
(opts: RelationalFieldOpts): ForeignKey;
}
export interface ManyCreator {
(toModelName: string, relatedName?: string): ManyToMany;
(opts: RelationalFieldOpts): ManyToMany;
}
export interface OneToOneCreator {
(toModelName: string, relatedName?: string): OneToOne;
(opts: RelationalFieldOpts): OneToOne;
}
export const attr: AttrCreator;
export const oneToOne: OneToOneCreator;
export const fk: FkCreator;
export const many: ManyCreator;
export interface FieldSpecMap {
[K: string]: Attribute | ForeignKey | ManyToMany | OneToOne;
}

23
types/redux-orm/helpers.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
/**
* Credits to Piotr Witek (http://piotrwitek.github.io) and utility-type project (https://github.com/piotrwitek/utility-types)
*/
export type Assign<T extends object, U extends object, I = Diff<T, U> & Intersection<U, T> & Diff<U, T>> = Pick<
I,
keyof I
>;
export type Diff<T extends object, U extends object> = Pick<T, Exclude<keyof T, keyof U>>;
export type PickByValue<T, ValueType> = Pick<T, { [Key in keyof T]: T[Key] extends ValueType ? Key : never }[keyof T]>;
export type Overwrite<T extends object, U extends object, I = Diff<T, U> & Intersection<U, T>> = Pick<I, keyof I>;
export type Optional<T extends object, K extends keyof T = keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type Intersection<T extends object, U extends object> = Pick<
T,
Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
>;
export type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];

View File

@ -1,215 +1,84 @@
// Type definitions for redux-orm 0.9
// Type definitions for redux-orm 0.13
// Project: https://github.com/redux-orm/redux-orm
// Definitions by: Andrey Goncharov <https://github.com/keenondrums>
// Tomasz Zabłocki <https://github.com/tomasz-zablocki>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
// TypeScript Version: 3.5
export interface ORMId {
id: string;
}
import { ORM, ORMOpts, OrmState } from './ORM';
import Model, {
CreateProps,
CustomInstanceProps,
IdKey,
IdOrModelLike,
IdType,
ModelField,
ModelFieldMap,
ModelType,
Ref,
RefPropOrSimple,
SessionBoundModel,
UpdateProps,
UpsertProps
} from './Model';
import QuerySet, {
LookupPredicate,
LookupProps,
LookupResult,
LookupSpec,
MutableQuerySet,
SortIteratee,
SortOrder
} from './QuerySet';
import { OrmSession } from './Session';
import { createDatabase, TableOpts, TableState } from './db';
import { attr, Attribute, FieldSpecMap, fk, ForeignKey, many, ManyToMany, OneToOne, oneToOne } from './fields';
import { createReducer, createSelector, defaultUpdater, ORMReducer, ORMSelector } from './redux';
export interface TableState<Item = any, Meta = any> {
items: string[];
itemsById: { [index: string]: Item };
meta: Meta;
}
export {
FieldSpecMap,
LookupResult,
LookupSpec,
LookupPredicate,
LookupProps,
TableOpts,
RefPropOrSimple,
ModelFieldMap,
CustomInstanceProps,
UpsertProps,
CreateProps,
UpdateProps,
ModelField,
SortIteratee,
OrmSession as Session,
SortOrder,
MutableQuerySet,
createDatabase,
createSelector,
createReducer,
defaultUpdater,
ORMSelector,
ORMReducer,
IdOrModelLike,
Ref,
SessionBoundModel,
IdKey,
IdType,
ModelType,
ORM,
OrmState,
ORMOpts,
TableState,
Model,
QuerySet,
Attribute,
OneToOne,
ForeignKey,
ManyToMany,
attr,
oneToOne,
fk,
many
};
export interface ORMCommonState {
[index: string]: TableState;
}
export type SessionWithModels<State extends ORMCommonState> = Session<State> & { [P in keyof State]: typeof Model };
export type ModelWithFields<Fields, Additional = {}, VirtualFields = {}> = Model<Fields, Additional, VirtualFields> & Fields & VirtualFields & Additional & ORMId;
// TODO: Refine me
export type ModelProps = any;
// TODO: Refine me
export interface DB {
getEmptyState: any;
query: any;
update: any;
describe: any;
}
// TODO: Refine me
export interface SchemaSpec {
tables: any;
}
// TODO: Refine me
export interface ORMOpts {
createDatabase: (schemaSpec: SchemaSpec) => any;
}
export interface ModelFields {
[index: string]: Attribute | ForeignKey | ManyToMany | OneToOne;
}
export interface ModelVirtualFields {
[index: string]: any;
}
export class ORM<State extends ORMCommonState = ORMCommonState> {
constructor(opts?: ORMOpts)
register(...model: Array<typeof Model>): void;
register<M>(...model: Array<M[keyof M]>): void;
registerManyToManyModelsFor(model: typeof Model): void;
get(modelName: string): typeof Model;
getModelClasses(): Array<typeof Model>;
isFieldInstalled(modelName: string, fieldName: string): boolean;
setFieldInstalled(modelName: string, fieldName: string): void;
generateSchemaSpec(): SchemaSpec;
getDatabase(): DB;
getEmptyState(): State;
session(state: State): SessionWithModels<State>;
mutableSession(state: State): SessionWithModels<State>;
private _attachQuerySetMethods(model: typeof Model): void;
private _setupModelPrototypes(models: Array<typeof Model>): void;
}
export class Model<Fields, Additional = {}, VirtualFields = {}> {
static readonly idAttribute: string;
static readonly session: SessionWithModels<any>;
static readonly _sessionData: any; // TODO
static readonly query: QuerySet<any>;
static modelName: string;
static fields: ModelFields;
static virtualFields: ModelVirtualFields;
static querySetClass: typeof QuerySet;
static toString(): string;
static options(): object;
static _getTableOpts(): object;
static markAccessed(): void;
static connec(session: Session<ORMCommonState>): void;
static getQuerySet(): QuerySet<any>;
static invalidateClassCache(): void;
static all(): QuerySet<any, any, any>;
static create<Fields>(props: Fields): ModelWithFields<Fields, any, any>;
static upsert<Fields>(props: Partial<Fields>): ModelWithFields<Fields, any, any>;
static withId(id: string): ModelWithFields<any, any, any>;
static hasId(id: string): boolean;
static _findDatabaseRows(lookupObj: object): any; // TODO
static get(lookupObj: object): ModelWithFields<any, any, any>;
static reducer(action: any, modelClass: typeof Model, session: SessionWithModels<ORMCommonState>): any;
readonly ref: Fields & Additional & ORMId;
constructor(props: ModelProps)
getClass(): string;
getId(): string;
toString(): string;
equals(otherModel: ModelWithFields<any, any, any>): boolean;
set(propertyName: string, value: any): void;
update(userMergeObj: Partial<Fields & Additional>): void;
refreshFromState(): void;
delete(): void;
private _onDelete(): void;
private _initFields(props: ModelProps): void;
private _refreshMany2Many(relations: any): void; // TODO
}
export type QuerySetClauses = any; // TODO
export type QuerySetOpts = any; // TODO
export class QuerySet<Fields, Additional = {}, VirtualFields = {}> {
static addSharedMethod(methodName: string): void;
constructor(modelClass: typeof Model, clauses: QuerySetClauses, opts: QuerySetOpts)
toString(): string;
toRefArray(): Array<Fields & Additional & ORMId>;
toModelArray(): Array<ModelWithFields<Fields, Additional, VirtualFields>>;
count(): number;
exists(): boolean;
at(index: string): ModelWithFields<Fields, Additional, VirtualFields> | undefined;
first(): ModelWithFields<Fields, Additional, VirtualFields> | undefined;
last(): ModelWithFields<Fields, Additional, VirtualFields> | undefined;
all(): QuerySet<Fields, Additional, VirtualFields>;
filter(lookupObj: object): QuerySet<Fields, Additional, VirtualFields>; // TODO
exclude(lookupObj: object): QuerySet<Fields, Additional, VirtualFields>; // TODO
orderBy(iteratees: any, orders: any): QuerySet<Fields, Additional, VirtualFields>; // TODO
update(mergeObj: Partial<Fields & Additional>): void;
delete(): void;
private _evaluate(): void;
private _new(clauses: QuerySetClauses, userOpts: QuerySetOpts): QuerySet<Fields, Additional, VirtualFields>;
}
export class Session<State extends ORMCommonState> {
readonly accessedModels: string[];
schema: ORM<State>;
db: DB;
initialState: State;
withMutations: boolean;
batchToken: any;
sessionBoundModels: Array<typeof Model>;
models: Array<typeof Model>;
state: State;
constructor(schema: ORM<State>, db: DB, state: State, withMutations: boolean, batchToken: any) // TODO
markAccessed(modelName: string): void;
getDataForModel(modelName: string): object;
applyUpdate(updateSpec: any): any; // TODO
query(querySpec: any): any; // TODO
}
export interface AttributeOpts {
getDefault?: () => any;
}
export class Attribute {
constructor(opts: AttributeOpts)
install(model: typeof Model, fieldName: string, orm: ORM): void;
}
export interface RelationalFieldOpts {
to: string;
relatedName?: string;
through?: string;
throughFields?: {
to: string;
from: string;
};
}
export class RelationalField {
constructor(toModelName: string, relatedName?: string)
constructor(opts: RelationalFieldOpts)
getClass: typeof RelationalField;
}
export class ForeignKey extends RelationalField {
install(model: typeof Model, fieldName: string, orm: ORM): void;
}
export class ManyToMany extends RelationalField {
install(model: typeof Model, fieldName: string, orm: ORM): void;
}
export class OneToOne extends RelationalField {
install(model: typeof Model, fieldName: string, orm: ORM): void;
}
export function attr(opts?: AttributeOpts): Attribute;
export function fk(toModelName: string, relatedName?: string): ForeignKey;
export function fk(opts: RelationalFieldOpts): ForeignKey;
export function many(toModelName: string, relatedName?: string): ManyToMany;
export function oneToOne(toModelName: string, relatedName?: string): OneToOne;
export function oneToOne(opts: RelationalFieldOpts): OneToOne;
export type Updater<State extends ORMCommonState> = (session: SessionWithModels<State>, action: any) => any;
export function createReducer<State extends ORMCommonState = ORMCommonState>(orm: ORM<State>, updater?: Updater<State>): (state: State, action: any) => State;
export type ORMSelector<State extends ORMCommonState, Result = any> = (session: SessionWithModels<State>, ...args: any[]) => Result;
export function createSelector<State extends ORMCommonState = ORMCommonState>(orm: ORM<State>, ...args: Array<ORMSelector<State>>): (state: State) => any;
export default Model;

View File

@ -1,78 +1,374 @@
import { attr, createSelector as createSelectorORM, ORMCommonState, ORMId, QuerySet, TableState, SessionWithModels, Model, ORM } from 'redux-orm';
import {
attr,
createSelector as createOrmSelector,
fk,
IdKey,
IdType,
many,
Model,
ModelType,
MutableQuerySet,
ORM,
OrmState,
QuerySet,
Ref
} from 'redux-orm';
// Model
export class Test extends Model<TestStateItem, FetchIndicatorState> {
static modelName = 'Test';
static fields = {
test: attr(),
isFetching: attr({ getDefault: () => false }),
id: attr()
};
interface CreateBookAction {
type: 'CREATE_BOOK';
payload: { coverArt?: string; title: string; publisher: number; authors?: string[] };
}
// core data which we do not have defaults for
export interface TestStateItem {
test: string;
interface DeleteBookAction {
type: 'DELETE_BOOK';
payload: { title: string };
}
// optional data we provide defaults for
export interface FetchIndicatorState {
isFetching: boolean;
type RootAction = CreateBookAction | DeleteBookAction;
interface BookFields {
title: string;
coverArt: string;
publisher: Publisher;
authors?: MutableQuerySet<Person>;
}
// id attr is added automatically by redux-orm therefore we have IORMId interface
export type TestState = TableState<TestStateItem & ORMId & FetchIndicatorState>;
export interface TestORMState extends ORMCommonState {
Test: TestState;
class Book extends Model<typeof Book, BookFields> {
static modelName = 'Book' as const;
static fields = {
title: attr(),
coverArt: attr({ getDefault: () => 'empty.png' }),
publisher: fk('Publisher', 'books'),
authors: many({ to: 'Person', relatedName: 'books', through: 'Authorship' })
};
static options = {
idAttribute: 'title' as const
};
static reducer(action: RootAction, Book: ModelType<Book>) {
switch (action.type) {
case 'CREATE_BOOK':
Book.create(action.payload);
break;
case 'DELETE_BOOK':
Book.filter(book => book.title === action.payload.title).delete();
break;
default:
break;
}
}
}
interface TestORMModels {
Test: typeof Test;
interface PersonFields {
id: string;
firstName: string;
lastName: string;
nationality?: string;
books?: MutableQuerySet<Book>;
}
const orm = new ORM<TestORMState>();
orm.register<TestORMModels>(Test);
// Reducer
interface TestDTO {
test: string;
class Person extends Model<typeof Person, PersonFields> {
static modelName = 'Person' as const;
static fields = {
id: attr(),
firstName: attr(),
lastName: attr(),
nationality: attr()
};
}
interface Action<P> {
type: string;
payload: P;
interface AuthorshipFields {
year?: number;
book: Book;
author: Person;
}
const reducerAddItem = (state: TestORMState, action: Action<TestDTO>): TestORMState => {
const session = orm.session(state);
session.Test.upsert<TestStateItem>(action.payload);
return session.state;
class Authorship extends Model<typeof Authorship, AuthorshipFields> {
static modelName = 'Authorship' as const;
static fields = {
year: attr(),
book: fk('Book'),
author: fk('Person')
};
}
interface PublisherFields {
index: number;
name: string;
books?: QuerySet<Book>;
}
class Publisher extends Model<typeof Publisher, PublisherFields> {
static modelName = 'Publisher' as const;
static fields = {
index: attr(),
name: attr()
};
static options = {
idAttribute: 'index' as const
};
}
const schema = { Book, Authorship, Person, Publisher };
type Schema = typeof schema;
// create ORM instance and register { Book, Publisher, Person, Authorship } schema
const ormFixture = () => {
const orm = new ORM<Schema>();
orm.register(Book, Authorship, Person, Publisher);
return orm;
};
// Selector
interface TestDisplayItem {
test: string;
}
type TestDisplayItemList = TestDisplayItem[];
// Just for the example below. Use real createSelector from reselect in your app
const createSelector = <S, P1, R>(param1Creator: (state: S) => P1, combiner: (param1: P1) => R): (state: S) => R => (state) =>
combiner(param1Creator(state));
interface RootState {
test: TestORMState;
}
export const makeGetTestDisplayList = () => {
const ormSelector = createSelectorORM<TestORMState>(orm, (session: SessionWithModels<TestORMState>) =>
(session.Test.all() as QuerySet<TestStateItem, FetchIndicatorState>)
.toRefArray()
.map((item) => ({ ...item }))
);
return createSelector<RootState, TestORMState, TestDisplayItemList>(
({ test }) => test,
ormSelector
);
// create ORM instance and acquire new session
const sessionFixture = () => {
const orm = ormFixture();
return orm.session(orm.getEmptyState());
};
// inferred optionality of ModelType.create argument properties
const argOptionalityAtModelCreation = () => {
const { Book, Publisher, Person } = 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' });
/**
* 1.B. `string` identifiers are mandatory
*/
const stringIdMissing = 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 });
/**
* 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' });
};
// ModelFields contribute to type constraints within ModelType.create arguments
const argPropertyTypeRestrictionsOnCreate = () => {
const { Book, Publisher, Person } = sessionFixture();
/** Keys of declared model fields interface contribute strict requirements regarding corresponding property types */
Book.create({ title: 'B1', publisher: 1, coverArt: 'foo.png', authors: ['A1'] });
/* Incompatible property types: */
Book.create({ title: 1, publisher: 1 }); // $ExpectError
Book.create({ title: 'B1', publisher: 'P1' }); // $ExpectError
Book.create({ title: 'B1', publisher: 1, coverArt: 4 }); // $ExpectError
Book.create({ title: 'B1', publisher: 1, authors: {} }); // $ExpectError
Book.create({ title: 'B1', publisher: 1, authors: () => null }); // $ExpectError
/**
* Properties associated to relational fields may be supplied with:
*
* - a primitive type matching id type of relation target
* - a Ref type derived from relation target
* - Model/SessionBoundModel instance matching relation target
* - a map containing {Idkey:IdType} entry, where IdKey/IdType are compatible with relation target id key:type signature
*
* In case of MutableQuerySets/many-to-many relationships, an array of union of above-mentioned types is accepted
*/
const authorModel = Person.create({ id: 'A1', firstName: 'A1', lastName: 'A1' });
const publisherModel = Publisher.create({ name: 'P1' });
Book.create({ title: 'B1', publisher: publisherModel, authors: [authorModel] });
Book.create({ title: 'B1', publisher: publisherModel.ref, authors: [authorModel.ref] });
Book.create({
title: 'B1',
publisher: { index: publisherModel.index },
authors: [{ id: authorModel.id }, 'A1', authorModel, authorModel.ref]
});
/** Id types are verified to match relation target */
Book.create({ title: 'B1', publisher: authorModel }); // $ExpectError
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 = () => {
const { Book, Publisher, Person } = sessionFixture();
/** Upsert requires id to be provided */
Book.upsert({ publisher: 1 }); // $ExpectError
// $ExpectType SessionBoundModel<Book, { title: string; publisher: number; }>
Book.upsert({ title: 'B1', publisher: 1 });
/* Incompatible property types: */
Book.upsert({ title: 4, publisher: 'P1' }); // $ExpectError
Book.upsert({ title: 'B1', publisher: 'P1' }); // $ExpectError
Book.upsert({ title: 'B1', publisher: 1, coverArt: 4 }); // $ExpectError
Book.upsert({ title: 'B1', publisher: 1, authors: {} }); // $ExpectError
Book.upsert({ title: 'B1', publisher: 1, authors: () => null }); // $ExpectError
/**
* Properties associated to relational fields may be supplied with:
*
* - a primitive type matching id type of relation target
* - a Ref type derived from relation target
* - Model/SessionBoundModel instance matching relation target
* - a map containing {Idkey:IdType} entry, where IdKey/IdType are compatible with relation target id key:type signature
*
* In case of MutableQuerySets/many-to-many relationships, an array of union of above-mentioned types is accepted
*/
const authorModel = Person.upsert({ id: 'A1', firstName: 'A1', lastName: 'A1' });
const publisherModel = Publisher.upsert({ name: 'P1', index: 1 });
Book.upsert({ title: 'B1', publisher: publisherModel, authors: [authorModel] });
Book.upsert({ title: 'B1', publisher: publisherModel.ref, authors: [authorModel.ref] });
Book.upsert({
title: 'B1',
publisher: { index: publisherModel.index },
authors: [{ id: authorModel.id }, 'A1', authorModel, authorModel.ref]
});
/** Id types are verified to match relation target */
Book.create({ title: 'B1', publisher: authorModel }); // $ExpectError
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 = () => {
const { Book, Person, Publisher } = sessionFixture();
// $ExpectType { Book: ModelType<Book>; Person: ModelType<Person>; Publisher: ModelType<Publisher>; }
const sessionBoundModels = { Book, Person, Publisher };
};
// 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 });
type basicBookKeys = Exclude<keyof typeof basicBook, keyof Model>; // $ExpectType "title" | "coverArt" | "publisher" | "authors"
const basicBookTitle = basicBook.title; // $ExpectType string
const authors = basicBook.authors; // $ExpectType MutableQuerySet<Person, {}> | undefined
const unknownPropertyError = basicBook.customProp; // $ExpectError
const customProp = { foo: 0, bar: true };
const extendedBook = Book.create({
title: 'extendedBook',
publisher: 1,
customProp
});
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 => {
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 = () => {
const { Book } = sessionFixture();
const booksQuerySet = Book.all();
// $ExpectType readonly Ref<Book>[]
const singleIteratee = booksQuerySet
.orderBy('title')
.orderBy(book => book.publisher, 'desc')
.orderBy(book => book.title, false)
.orderBy('publisher', 'asc')
.orderBy('publisher', true)
.toRefArray();
// $ExpectType readonly Ref<Book>[]
const arrayIteratee = booksQuerySet
.orderBy(['title'], ['asc'])
.orderBy(['publisher', 'title'], [true, 'desc'])
.orderBy([book => book.title], ['desc'])
.orderBy(['title'])
.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
};
const selectors = () => {
// test fixture, use reselect.createSelector in production code
const createSelector = <S, OS extends OrmState<any>, Result extends any>(
param1Creator: (state: S) => OS,
combiner: (param1: OS) => Result
): ((state: S) => Result) => state => combiner(param1Creator(state));
const orm = ormFixture();
const ormSelector = createOrmSelector(orm, session => session.Book.all().toRefArray()[0]);
interface RootState {
db: OrmState<Schema>;
}
const selector = createSelector<RootState, OrmState<Schema>, Ref<Book>>(
({ db }) => db,
ormSelector
);
createSelector<RootState, OrmState<Schema>, Ref<Person>>(
({ db }) => db,
ormSelector // $ExpectError
);
selector({ db: orm.getEmptyState() }); // $ExpectType Ref<Book>
};

25
types/redux-orm/redux.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
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 type defaultUpdater<I extends IndexedModelClasses<any>, TAction extends any = any> = (
session: OrmSession<I>,
action: TAction
) => void;
export function createReducer<I extends IndexedModelClasses<any>, 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 function createSelector<I extends IndexedModelClasses<any>, Result extends any = any>(
orm: ORM<I>,
ormSelector: ORMSelector<I, Result>
): (state: OrmState<I>) => Result;

View File

@ -1,23 +1,30 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"lib": ["es6"],
"target": "es6",
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"typeRoots": ["../"],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"redux-orm-tests.ts"
"redux-orm-tests.ts",
"db/index.d.ts",
"db/Database.d.ts",
"db/Table.d.ts",
"helpers.d.ts",
"redux.d.ts",
"fields.d.ts",
"ORM.d.ts",
"Model.d.ts",
"QuerySet.d.ts",
"Session.d.ts"
]
}