import { DateField } from './DateField'; import { Doc, FieldType, FieldResult, Opt } from './Doc'; import { List } from './List'; import { ProxyField } from './Proxy'; import { RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { ScriptField } from './ScriptField'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; // eslint-disable-next-line no-use-before-define export type ToConstructor = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List ? ListSpec : new (...args: never[]) => T; export type DefaultFieldConstructor = { type: ToConstructor; defaultVal: T; }; // type ListSpec = { List: ToContructor> | ListSpec> }; export type ListSpec = { List: ToConstructor }; export type InterfaceValue = ToConstructor | ListSpec | DefaultFieldConstructor | ((doc?: Doc) => never); 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; // eslint-disable-next-line @typescript-eslint/no-unused-vars T extends DefaultFieldConstructor ? never : T extends { new (...args: never[]): List } ? never : T extends { new (...args: never[]): infer R } ? R : T extends (doc?: Doc) => infer R ? R : never; export interface Interface { [key: string]: InterfaceValue; } export type ToInterface = { [P in Exclude]: T[P] extends DefaultFieldConstructor ? Exclude, undefined> : FieldResult>; }; export type Head = T extends [unknown, ...unknown[]] ? T[0] : Interface; export type Tail = ((...t: T) => unknown) extends (_: unknown, ...tail: infer TT) => unknown ? TT : []; export type HasTail = T extends [] | [unknown] ? 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 RefField ? unknown : T; export type CastCtor = ToConstructor | ListSpec; type WithoutList = T extends List ? (R extends RefField ? (R | Promise)[] : R[]) : T; export function Cast(field: FieldResult, ctor: T): FieldResult> | undefined; export function Cast(field: FieldResult, ctor: T, defaultVal: WithoutList> | null): WithoutList> | undefined; 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 ToType) as ToType) : 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 ToType; } // eslint-disable-next-line @typescript-eslint/no-explicit-any } else if (field instanceof (ctor as any)) { return field as ToType; // eslint-disable-next-line @typescript-eslint/no-explicit-any } else if (field instanceof ProxyField && field.value instanceof (ctor as any)) { return field.value as ToType; } } return defaultVal === null ? undefined : defaultVal; } export function toList(doc: Doc | Doc[]) { return doc instanceof Doc ? [doc] : doc; } // prettier-ignore export function DocCast(field: FieldResult, defaultVal?: Doc) { return ((doc: Doc | undefined) => (doc && !(doc instanceof Promise) ? doc : defaultVal))(Cast(field, Doc, null)); } export function NumCast (field: FieldResult, defaultVal: number | null = 0) { return Cast(field, 'number', defaultVal)!; } // prettier-ignore export function StrCast (field: FieldResult, defaultVal: string | null = '') { return Cast(field, 'string', defaultVal)!; } // prettier-ignore export function BoolCast (field: FieldResult, defaultVal: boolean | null = false) { return Cast(field, 'boolean', defaultVal)!; } // prettier-ignore export function DateCast (field: FieldResult, defaultVal: DateField | null = null) { return Cast(field, DateField, defaultVal); } // prettier-ignore export function RTFCast (field: FieldResult, defaultVal: RichTextField | null = null){ return Cast(field, RichTextField, defaultVal); } // prettier-ignore export function ScriptCast(field: FieldResult, defaultVal: ScriptField | null = null) { return Cast(field, ScriptField, defaultVal); } // prettier-ignore export function CsvCast (field: FieldResult, defaultVal: CsvField | null = null) { return Cast(field, CsvField, defaultVal); } // prettier-ignore export function WebCast (field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } // prettier-ignore export function VideoCast (field: FieldResult, defaultVal: VideoField | null = null) { return Cast(field, VideoField, defaultVal); } // prettier-ignore export function AudioCast (field: FieldResult, defaultVal: AudioField | null = null) { return Cast(field, AudioField, defaultVal); } // prettier-ignore export function PDFCast (field: FieldResult, defaultVal: PdfField | null = null) { return Cast(field, PdfField, defaultVal); } // prettier-ignore export function ImageCast (field: FieldResult, defaultVal: ImageField | null = null) { return Cast(field, ImageField, defaultVal); } // prettier-ignore export function ImageCastToNameType(field: FieldResult, defaultVal: ImageField | null = null) { const href = ImageCast(field, defaultVal)?.url.href; return href ? [href.replace(/.[^.]*$/, ''), href.split('.').lastElement()] : ['', '']; } export function ImageCastWithSuffix(field: FieldResult, suffix: string, defaultVal: ImageField | null = null) { const [name, type] = ImageCastToNameType(field, defaultVal); return name ? `${name}${suffix}.${type}` : null; } 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); }, }; }