import { Field, Opt, FieldResult, Doc } from './Doc'; import { List } from './List'; import { RefField } from './RefField'; import { DateField } from './DateField'; import { ScriptField } from './ScriptField'; import { URLField, WebField, ImageField, CsvField } from './URLField'; import { TextField } from '@material-ui/core'; import { RichTextField } from './RichTextField'; export type ToType = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends ListSpec ? List : // T extends { new(...args: any[]): infer R } ? (R | Promise) : never; T extends DefaultFieldConstructor ? never : T extends { new (...args: any[]): List } ? never : T extends { new (...args: any[]): infer R } ? R : T extends (doc?: Doc) => infer R ? R : never; export type ToConstructor = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List ? ListSpec : new (...args: any[]) => T; export type ToInterface = { [P in Exclude]: T[P] extends DefaultFieldConstructor ? Exclude, undefined> : FieldResult>; }; // type ListSpec = { List: ToContructor> | ListSpec> }; export type ListSpec = { List: ToConstructor }; export type DefaultFieldConstructor = { type: ToConstructor; defaultVal: T; }; // type ListType = { 0: List>>, 1: ToType> }[HasTail extends true ? 0 : 1]; export type Head = T extends [any, ...any[]] ? T[0] : never; export type Tail = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : []; export type HasTail = T extends [] | [any] ? false : true; export type InterfaceValue = ToConstructor | ListSpec | DefaultFieldConstructor | ((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 | ListSpec; } export type WithoutRefField = T extends RefField ? never : T; export type CastCtor = ToConstructor | ListSpec; export function Cast(field: FieldResult, ctor: T): FieldResult>; export function Cast(field: FieldResult, ctor: T, defaultVal: WithoutList>> | null): WithoutList>; export function Cast(field: FieldResult, ctor: T, defaultVal?: ToType | null): FieldResult> | 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') { if (typeof field === ctor) { return field as ToType; } } else if (typeof ctor === 'object') { if (field instanceof List) { return field as any; } } else if (field instanceof (ctor as any)) { return field as ToType; } } return defaultVal === null ? undefined : defaultVal; } export function DocCast(field: FieldResult, defaultVal?: Doc) { const doc = Cast(field, Doc, null); return doc && !(doc instanceof Promise) ? doc : (defaultVal as Doc); } export function NumCast(field: FieldResult, defaultVal: number | null = 0) { return Cast(field, 'number', defaultVal); } export function StrCast(field: FieldResult, defaultVal: string | null = '') { return Cast(field, 'string', defaultVal); } export function BoolCast(field: FieldResult, defaultVal: boolean | null = false) { return Cast(field, 'boolean', defaultVal); } export function DateCast(field: FieldResult) { return Cast(field, DateField, null); } export function RTFCast(field: FieldResult) { return Cast(field, RichTextField, null); } export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) { return Cast(field, ScriptField, defaultVal); } export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) { return Cast(field, CsvField, defaultVal); } export function WebCast(field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) { return Cast(field, ImageField, defaultVal); } type WithoutList = T extends List ? (R extends RefField ? (R | Promise)[] : R[]) : T; export function FieldValue>(field: FieldResult, defaultValue: U): WithoutList; export function FieldValue(field: FieldResult): Opt; export function FieldValue(field: FieldResult, defaultValue?: T): Opt { return field instanceof Promise || field === undefined ? defaultValue : field; } export interface PromiseLike { then(callback: (field: Opt) => void): void; } export function PromiseValue(field: FieldResult): PromiseLike> { if (field instanceof Promise) return field as Promise>; return { then(cb: (field: Opt) => void) { return cb(field); }, }; }