diff options
Diffstat (limited to 'src/fields/Types.ts')
-rw-r--r-- | src/fields/Types.ts | 90 |
1 files changed, 50 insertions, 40 deletions
diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 337e8ca21..26196d15d 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -1,5 +1,5 @@ import { DateField } from './DateField'; -import { Doc, Field, FieldResult, Opt } from './Doc'; +import { Doc, FieldType, FieldResult, Opt } from './Doc'; import { List } from './List'; import { ProxyField } from './Proxy'; import { RefField } from './RefField'; @@ -7,63 +7,69 @@ import { RichTextField } from './RichTextField'; import { ScriptField } from './ScriptField'; import { CsvField, ImageField, WebField } from './URLField'; +// eslint-disable-next-line no-use-before-define +export type ToConstructor<T extends FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T; + +export type DefaultFieldConstructor<T extends FieldType> = { + type: ToConstructor<T>; + defaultVal: T; +}; +// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> }; +export type ListSpec<T extends FieldType> = { List: ToConstructor<T> }; + +export type InterfaceValue = ToConstructor<FieldType> | ListSpec<FieldType> | DefaultFieldConstructor<FieldType> | ((doc?: Doc) => any); + export type ToType<T extends InterfaceValue> = T extends 'string' ? string : T extends 'number' - ? number - : T extends 'boolean' - ? boolean - : T extends ListSpec<infer U> - ? List<U> - : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never; - T extends DefaultFieldConstructor<infer _U> - ? never - : T extends { new (...args: any[]): List<Field> } - ? never - : T extends { new (...args: any[]): infer R } - ? R - : T extends (doc?: Doc) => infer R - ? R - : never; - -export type ToConstructor<T extends Field> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T; + ? number + : T extends 'boolean' + ? boolean + : T extends ListSpec<infer U> + ? List<U> + : // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + T extends DefaultFieldConstructor<infer _U> + ? never + : T extends { new (...args: any[]): List<FieldType> } + ? never + : T extends { new (...args: any[]): infer R } + ? R + : T extends (doc?: Doc) => infer R + ? R + : never; +export interface Interface { + [key: string]: InterfaceValue; + // [key: string]: ToConstructor<Field> | ListSpec<Field[]>; +} export type ToInterface<T extends Interface> = { [P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>; }; -// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> }; -export type ListSpec<T extends Field> = { List: ToConstructor<T> }; - -export type DefaultFieldConstructor<T extends Field> = { - type: ToConstructor<T>; - defaultVal: T; -}; - // type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1]; export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never; export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : []; export type HasTail<T extends any[]> = T extends [] | [any] ? false : true; +// TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial +export type WithoutRefField<T extends FieldType> = T extends RefField ? never : T; -export type InterfaceValue = ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field> | ((doc?: Doc) => any); -//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial -export interface Interface { - [key: string]: InterfaceValue; - // [key: string]: ToConstructor<Field> | ListSpec<Field[]>; -} -export type WithoutRefField<T extends Field> = T extends RefField ? never : T; +export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>; -export type CastCtor = ToConstructor<Field> | ListSpec<Field>; +type WithoutList<T extends FieldType> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T; export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>; +// eslint-disable-next-line no-redeclare export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>; +// eslint-disable-next-line no-redeclare export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined { if (field instanceof Promise) { return defaultVal === undefined ? (field.then(f => Cast(f, ctor) as any) as any) : defaultVal === null ? undefined : defaultVal; } if (field !== undefined && !(field instanceof Promise)) { if (typeof ctor === 'string') { + // eslint-disable-next-line valid-typeof if (typeof field === ctor) { return field as ToType<T>; } @@ -80,6 +86,10 @@ export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal return defaultVal === null ? undefined : defaultVal; } +export function toList(doc: Doc | Doc[]) { + return doc instanceof Doc ? [doc] : doc; +} + export function DocCast(field: FieldResult, defaultVal?: Doc) { const doc = Cast(field, Doc, null); return doc && !(doc instanceof Promise) ? doc : (defaultVal as Doc); @@ -116,18 +126,18 @@ export function ImageCast(field: FieldResult, defaultVal: ImageField | null = nu return Cast(field, ImageField, defaultVal); } -type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T; - -export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>; -export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>; -export function FieldValue<T extends Field>(field: FieldResult<T>, defaultValue?: T): Opt<T> { +export function FieldValue<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>; +// eslint-disable-next-line no-redeclare +export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>; +// eslint-disable-next-line no-redeclare +export function FieldValue<T extends FieldType>(field: FieldResult<T>, defaultValue?: T): Opt<T> { return field instanceof Promise || field === undefined ? defaultValue : field; } export interface PromiseLike<T> { then(callback: (field: Opt<T>) => void): void; } -export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> { +export function PromiseValue<T extends FieldType>(field: FieldResult<T>): PromiseLike<Opt<T>> { if (field instanceof Promise) return field as Promise<Opt<T>>; return { then(cb: (field: Opt<T>) => void) { |