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);
},
};
}
|