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
131
132
|
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 } from './URLField';
import { TextField } from '@material-ui/core';
import { RichTextField } from './RichTextField';
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;
T extends DefaultFieldConstructor<infer _U>
? never
: T extends { new (...args: any[]): List<Field> }
? never
: T extends { new (...args: any[]): infer R }
? R
: T extends (doc?: Doc) => infer R
? R
: never;
export type ToConstructor<T extends Field> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List<infer U> ? ListSpec<U> : new (...args: any[]) => T;
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]>>;
};
// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
export type ListSpec<T extends Field> = { List: ToConstructor<T> };
export type DefaultFieldConstructor<T extends Field> = {
type: ToConstructor<T>;
defaultVal: T;
};
// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
export type Tail<T extends any[]> = ((...t: T) => any) extends (_: any, ...tail: infer TT) => any ? TT : [];
export type HasTail<T extends any[]> = T extends [] | [any] ? false : true;
export type InterfaceValue = ToConstructor<Field> | ListSpec<Field> | DefaultFieldConstructor<Field> | ((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<Field> | ListSpec<Field[]>;
}
export type WithoutRefField<T extends Field> = T extends RefField ? never : T;
export type CastCtor = ToConstructor<Field> | ListSpec<Field>;
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
export function Cast<T extends CastCtor>(field: FieldResult, ctor: T, defaultVal: WithoutList<WithoutRefField<ToType<T>>> | null): WithoutList<ToType<T>>;
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 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<T>;
}
} else if (typeof ctor === 'object') {
if (field instanceof List) {
return field as any;
}
} else if (field instanceof (ctor as any)) {
return field as ToType<T>;
}
}
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 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 Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
export function FieldValue<T extends Field>(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 Field>(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);
},
};
}
|