Skip to content

Commit

Permalink
[enhance] Improve normalizr type expressiveness
Browse files Browse the repository at this point in the history
  • Loading branch information
ntucker committed Oct 9, 2019
1 parent 139fb66 commit 5058996
Showing 1 changed file with 47 additions and 23 deletions.
70 changes: 47 additions & 23 deletions src/resource/normal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,29 @@ declare namespace schemas {
) => S;
type EntityMap<T = any> = { [key: string]: Entity<T> };

export class Array<T = any> {
constructor(
definition: Schema<T>,
schemaAttribute?: string | SchemaFunction,
);
export class Array<S extends Schema = Schema<any>> {
private _identifier: 'Array';
constructor(definition: S, schemaAttribute?: string | SchemaFunction);
define(definition: Schema): void;
readonly schema: S;
}

export interface EntityOptions<T = any> {
idAttribute?: string | SchemaFunction;
export interface EntityOptions<T = any, ID = string> {
idAttribute?: string | SchemaFunction<ID>;
mergeStrategy?: MergeFunction;
processStrategy?: StrategyFunction<T>;
}

export class Entity<T = any> {
constructor(key: string, definition?: Schema, options?: EntityOptions<T>);
export class Entity<T = any, K extends string = string> {
private _identifier: 'Entity';
constructor(
key: K,
definition?: Schema,
options?: EntityOptions<T, string>,
);
define(definition: Schema): void;
key: string;
getId: SchemaFunction;
readonly key: K;
getId: SchemaFunction<string>;
_processStrategy: StrategyFunction<T>;
}

Expand All @@ -41,9 +45,11 @@ declare namespace schemas {
> {
constructor(definition: O);
define(definition: Schema): void;
readonly schema: O;
}

export class Union<Choices extends EntityMap = any> {
private _identifier: 'Union';
constructor(
definition: Choices,
schemaAttribute:
Expand All @@ -53,23 +59,26 @@ declare namespace schemas {
define(definition: Schema): void;
inferSchema: SchemaAttributeFunction<Choices[keyof Choices]>;
getSchemaAttribute: SchemaFunction<keyof Choices>;
readonly schema: Choices;
}

export class Values<Choices extends EntityMap | Schema = any> {
private _identifier: 'Values';
constructor(
definition: Choices,
schemaAttribute?: Choices extends EntityMap<infer T>
? keyof T | SchemaFunction<keyof Choices>
: undefined,
);
define(definition: Schema): void;
isSingleSchema: Choices extends EntityMap ? false : true;
readonly isSingleSchema: Choices extends EntityMap ? false : true;
inferSchema: SchemaAttributeFunction<
Choices extends EntityMap ? Choices[keyof Choices] : Choices
>;
getSchemaAttribute: Choices extends EntityMap
? SchemaFunction<keyof Choices>
: false;
readonly schema: Choices;
}
}

Expand Down Expand Up @@ -103,11 +112,14 @@ interface SchemaArray<T> extends Array<SchemaDetail<T>> {}
export type SchemaDetail<T> =
| schemas.Entity<T>
| schemas.Object<T>
| schemas.Union<{ [key: string]: schemas.Entity<T> }>
| schemas.Values<{ [key: string]: schemas.Entity<T> } | schemas.Entity<T>>
| schemas.Union<schemas.EntityMap<T>>
| schemas.Values<schemas.Entity<T> | SchemaObjectOne<T>>
| SchemaObjectOne<T>;

export type SchemaList<T> = SchemaArray<T> | SchemaObjectMany<T>;
export type SchemaList<T> =
| SchemaArray<T>
| SchemaObjectMany<T>
| schemas.Array<SchemaDetail<T>>;

export type Schema<T = any> = SchemaDetail<T> | SchemaList<T>;

Expand All @@ -128,7 +140,7 @@ export function denormalize<S extends Schema>(
entities: any,
): Denormalized<S>;

export type Denormalized<S> = S extends schemas.Entity<infer T>
export type DenormalizedCore<S> = S extends schemas.Entity<infer T>
? T
: S extends schemas.Values<infer Choices>
? Record<
Expand All @@ -142,31 +154,43 @@ export type Denormalized<S> = S extends schemas.Entity<infer T>
: S extends schemas.Union<infer Choices>
? (Choices[keyof Choices] extends schemas.Entity<infer T> ? T : never) // TODO: typescript 3.7 make this recursive instead
: S extends schemas.Object<any, infer O>
? { [K in keyof O]: O[K] extends Schema ? Denormalized<O[K]> : O[K] }
? { [K in keyof O]: O[K] extends Schema ? DenormalizedCore<O[K]> : O[K] }
: S extends { [key: string]: any }
? { [K in keyof S]: S[K] extends Schema ? Denormalized<S[K]> : S[K] }
? { [K in keyof S]: S[K] extends Schema ? DenormalizedCore<S[K]> : S[K] }
: S;

type UnionResult<Choices extends schemas.EntityMap> = {
id: ReturnType<Choices[keyof Choices]['getId']>;
schema: keyof Choices;
};

export type ResultType<S> = S extends schemas.Entity
? ReturnType<S['getId']>
export type Denormalized<S> = Extract<S, schemas.Entity<any>> extends never
? (S extends (infer I)[] | schemas.Array<infer I>
? DenormalizedCore<Extract<I, schemas.Entity<any>>>[]
: DenormalizedCore<S>)
: DenormalizedCore<Extract<S, schemas.Entity<any>>>;

export type ResultTypeCore<S> = S extends schemas.Entity<any>
? ReturnType<S['getId']> // should always be string
: S extends schemas.Values<infer Choices>
? Record<
string,
Choices extends schemas.EntityMap
? UnionResult<Choices>
: Choices extends schemas.Entity
: Choices extends schemas.Entity<any>
? ReturnType<Choices['getId']> // TODO: typescript 3.7 let's us make this recursive
: never
>
: S extends schemas.Union<infer Choices>
? UnionResult<Choices>
: S extends schemas.Object<any, infer O>
? { [K in keyof O]: O[K] extends Schema ? ResultType<O[K]> : O[K] }
? { [K in keyof O]: O[K] extends Schema ? ResultTypeCore<O[K]> : O[K] }
: S extends { [key: string]: any }
? { [K in keyof S]: S[K] extends Schema ? ResultType<S[K]> : S[K] }
? { [K in keyof S]: S[K] extends Schema ? ResultTypeCore<S[K]> : S[K] }
: S;

export type ResultType<S> = Extract<S, schemas.Entity<any>> extends never
? (S extends (infer I)[] | schemas.Array<infer I>
? ResultTypeCore<Extract<I, schemas.Entity<any>>>[]
: ResultTypeCore<S>)
: ResultTypeCore<Extract<S, schemas.Entity<any>>>;

0 comments on commit 5058996

Please sign in to comment.