Skip to content

Commit e91e1ac

Browse files
committed
refactor: add futuristic type inference
1 parent 94523e9 commit e91e1ac

2 files changed

Lines changed: 159 additions & 4 deletions

File tree

src/helpers.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,28 +51,31 @@ export function number(opts?: NumberOptions): NumberValidator {
5151
};
5252
}
5353

54-
export function list<V extends Validator>(shape: V[], opts?: ListOptions): ListValidator {
54+
export function list<V extends Validator[]>(shape: V, opts?: ListOptions): ListValidator {
5555
return {
5656
...base($list, optional(opts?.optional)),
5757
shape,
5858
};
5959
}
6060

61-
export function listof(of: Validator, opts?: ListofOptions): ListofValidator {
61+
export function listof<V extends Validator>(of: V, opts?: ListofOptions): ListofValidator {
6262
return {
6363
...base($listof, optional(opts?.optional)),
6464
of,
6565
};
6666
}
6767

68-
export function record(shape: { [key: string]: Validator }, opts?: RecordOptions): RecordValidator {
68+
export function record<S extends { [key: string]: Validator }>(
69+
shape: S,
70+
opts?: RecordOptions
71+
): RecordValidator {
6972
return {
7073
...base($record, optional(opts?.optional)),
7174
shape,
7275
};
7376
}
7477

75-
export function recordof(of: Validator, opts?: RecordofOptions): RecordofValidator {
78+
export function recordof<V extends Validator>(of: V, opts?: RecordofOptions): RecordofValidator {
7679
return {
7780
...base($recordof, optional(opts?.optional)),
7881
of,

src/t.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
type Opt<T> = T & { optional: true };
2+
type Req<T> = T & { optional: false };
3+
type V<T> = Opt<T> | Req<T>;
4+
5+
interface StringOptions {
6+
type: 'string';
7+
maxLength?: [number, string];
8+
minLength?: [number, string];
9+
pattern?: [RegExp, string];
10+
}
11+
type StringValidator = V<StringOptions>;
12+
13+
interface NumberOptions {
14+
type: 'number';
15+
max?: [number, string];
16+
min?: [number, string];
17+
is?: ['integer' | 'float', string];
18+
}
19+
type NumberValidator = V<NumberOptions>;
20+
21+
interface BooleanValidator {
22+
type: 'boolean';
23+
optional: boolean;
24+
}
25+
26+
interface ListValidator<T extends Validator[]> {
27+
type: 'list';
28+
optional: boolean;
29+
shape: T;
30+
}
31+
32+
interface ListofValidator<T extends Validator> {
33+
type: 'listof';
34+
optional: boolean;
35+
of: T;
36+
}
37+
38+
interface RecordValidator<T extends Schema> {
39+
type: 'record';
40+
optional: boolean;
41+
shape: T;
42+
}
43+
44+
interface RecordofValidator<T extends Validator> {
45+
type: 'recordof';
46+
optional: boolean;
47+
of: T;
48+
}
49+
50+
type Validator =
51+
| StringValidator
52+
| NumberValidator
53+
| BooleanValidator
54+
| ListofValidator<Validator>
55+
| ListValidator<Validator[]>
56+
| RecordValidator<Schema>
57+
| RecordofValidator<Validator>;
58+
59+
interface Schema {
60+
[key: string]: Validator;
61+
}
62+
63+
type DataFrom<S extends Schema> = {
64+
[K in keyof S]: InferDataType<S[K]>;
65+
};
66+
67+
type InferTypeWithOptional<T, U> = T extends Opt<T> ? U | void : U;
68+
69+
type InferDataType<T> = T extends StringValidator
70+
? InferTypeWithOptional<T, string>
71+
: T extends NumberValidator
72+
? InferTypeWithOptional<T, number>
73+
: T extends BooleanValidator
74+
? boolean
75+
: T extends ListValidator<infer U>
76+
? { [K in keyof U]: InferDataType<U[K]> }
77+
: T extends ListofValidator<infer V>
78+
? InferDataType<V>[]
79+
: T extends RecordValidator<infer S>
80+
? { [K in keyof S]: InferDataType<S[K]> }
81+
: T extends RecordofValidator<infer V>
82+
? Record<string, InferDataType<V>>
83+
: never;
84+
85+
function string(config: { optional: true } & Omit<StringOptions, 'type'>): Opt<StringOptions>;
86+
function string(config: { optional: false } & Omit<StringOptions, 'type'>): Req<StringOptions>;
87+
function string(config: Omit<StringOptions, 'type'>): Req<StringOptions>;
88+
function string(): Req<StringOptions>;
89+
function string(config?: { optional?: boolean } & Omit<StringOptions, 'type'>): StringValidator {
90+
return {
91+
type: 'string',
92+
optional: Boolean(config?.optional),
93+
};
94+
}
95+
96+
function number(config: { optional: true } & Omit<NumberOptions, 'type'>): Opt<NumberOptions>;
97+
function number(config: { optional: false } & Omit<NumberOptions, 'type'>): Req<NumberOptions>;
98+
function number(config: Omit<NumberOptions, 'type'>): Req<NumberOptions>;
99+
function number(): Req<NumberOptions>;
100+
function number(config?: { optional?: boolean } & Omit<NumberOptions, 'type'>): NumberValidator {
101+
return {
102+
type: 'number',
103+
optional: Boolean(config?.optional),
104+
};
105+
}
106+
107+
const boolean = (config?: { optional: boolean }): BooleanValidator => ({
108+
type: 'boolean',
109+
optional: Boolean(config?.optional),
110+
});
111+
112+
const list = <T extends Validator[]>(
113+
list: T,
114+
config?: { optional: boolean }
115+
): ListValidator<T> => ({ type: 'list', shape: list, optional: Boolean(config?.optional) });
116+
117+
const listof = <T extends Validator>(v: T, config?: { optional: boolean }): ListofValidator<T> => ({
118+
type: 'listof',
119+
of: v,
120+
optional: Boolean(config?.optional),
121+
});
122+
123+
const record = <T extends Schema>(s: T, config?: { optional: boolean }): RecordValidator<T> => ({
124+
type: 'record',
125+
shape: s,
126+
optional: Boolean(config?.optional),
127+
});
128+
129+
const recordof = <T extends Validator>(
130+
v: T,
131+
config?: { optional: boolean }
132+
): RecordofValidator<T> => ({ type: 'recordof', of: v, optional: Boolean(config?.optional) });
133+
134+
/* TEST USE-CASE */
135+
const create = <S extends Schema>(_: S): DataFrom<S> => ({} as DataFrom<S>); // mock
136+
const res = create({
137+
id: string({ optional: true, maxLength: [10, 'lksjdf'] }),
138+
name: string({ maxLength: [20, 'lkasjdf'] }),
139+
age: number(),
140+
tags: listof(string()),
141+
stuff: list([string(), number(), string()]),
142+
friends: recordof(record({ name: string(), age: number() })),
143+
meta: record({
144+
created: number(),
145+
updated: number(),
146+
id: string(),
147+
nested: record({
148+
y: string(),
149+
}),
150+
}),
151+
});
152+
/* ************ */

0 commit comments

Comments
 (0)