aboutsummaryrefslogtreecommitdiff
path: root/src/fields/Types.ts
blob: e6755f82831e61ac51282387d4f42b7ea3127c07 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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 FieldType> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: never[]) => 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) => never);

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;
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            T extends DefaultFieldConstructor<infer _U>
            ? never
            : T extends { new (...args: never[]): List<FieldType> }
              ? 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<T extends Interface> = {
    [P in Exclude<keyof T, 'proto'>]: T[P] extends DefaultFieldConstructor<infer F> ? Exclude<FieldResult<F>, undefined> : FieldResult<ToType<T[P]>>;
};

export type Head<T extends unknown[]> = T extends [unknown, ...unknown[]] ? T[0] : Interface;
export type Tail<T extends unknown[]> = ((...t: T) => unknown) extends (_: unknown, ...tail: infer TT) => unknown ? TT : [];
export type HasTail<T extends unknown[]> = 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 FieldType> = T extends RefField ? unknown : T;

export type CastCtor = ToConstructor<FieldType> | ListSpec<FieldType>;

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>> | undefined;
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<ToType<T>> | null): WithoutList<ToType<T>> | undefined;
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 ToType<T>) as ToType<T>) : defaultVal === null ? undefined : defaultVal;
    }
    if (field !== undefined && !(field instanceof Promise)) {
        if (typeof ctor === 'string') {
            if (typeof field === ctor) {
                return field as ToType<T>;
            }
        } else if (typeof ctor === 'object') {
            if (field instanceof List) {
                return field as ToType<T>;
            }
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
        } else if (field instanceof (ctor as any)) {
            return field as ToType<T>;
            // 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<T>;
        }
    }
    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<T extends FieldType, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
export function FieldValue<T extends FieldType>(field: FieldResult<T>): Opt<T>;
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 FieldType>(field: FieldResult<T>): PromiseLike<Opt<T>> {
    if (field instanceof Promise) return field as Promise<Opt<T>>;
    return {
        then(cb: (field: Opt<T>) => void) {
            return cb(field);
        },
    };
}