aboutsummaryrefslogtreecommitdiff
path: root/src/fields/Types.ts
blob: ba2e9bb6fc87c9c719f009ef864446c1fbebaff5 (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
129
130
import { DateField } from './DateField';
import { Doc, FieldType, FieldResult, Opt } from './Doc';
import { InkField } from './InkField';
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 InkCast   (field: FieldResult, defaultVal: InkField | null = null)     { return Cast(field, InkField, 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);
        },
    };
}