Skip to content

Commit a827591

Browse files
committed
feat: implement better type inference | less work for the user
BREAKING CHANGE: infers data type automatically for both JS & TS
1 parent f922048 commit a827591

9 files changed

Lines changed: 176 additions & 296 deletions

File tree

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module.exports = {
22
rules: {
33
eqeqeq: 'off',
4+
'no-redeclare': 'off',
5+
'@typescript-eslint/no-redeclare': ['error', { ignoreDeclarationMerge: true }],
46
},
57
};

src/createSchema.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@ import { isPlainObject, ObjectKeys } from './utils';
22
import { createErrors } from './createErrors';
33
import { DATAERR, $record, SCHEMAERR } from './constants';
44
import invariant from 'tiny-invariant';
5-
import { RecordValidator } from './validatorTypes';
6-
import { SchemaFrom, ErrorsFrom } from './type-utils';
5+
import { RecordValidator, Schema } from './validatorTypes';
6+
import { DataFrom, ErrorsFrom } from './type-utils';
77
import { traverse as _traverse, Visitor } from './traverse';
88

9-
export function createSchema<T extends { [key: string]: any }>(_schema: SchemaFrom<T>) {
9+
export function createSchema<T extends Schema>(_schema: T) {
1010
invariant(isPlainObject(_schema), SCHEMAERR);
1111
const schema = Object.freeze({ ..._schema });
1212

1313
function validate(data: any, eager = false) {
1414
const errors = createErrors(schema, data, eager);
15-
return ObjectKeys(errors).length > 0 ? (errors as ErrorsFrom<T>) : null;
15+
return ObjectKeys(errors).length > 0 ? (errors as ErrorsFrom<DataFrom<T>>) : null;
1616
}
1717

18-
function is(data: any): data is T {
18+
function is(data: any): data is DataFrom<T> {
1919
const errors = validate(data, true);
2020
return !errors && isPlainObject(data);
2121
}
2222

23-
function embed(config = { optional: false }): RecordValidator {
23+
function embed(config = { optional: false }): RecordValidator<T> {
2424
return { type: $record, shape: schema, ...config };
2525
}
2626

27-
function produce(data: any): T {
27+
function produce(data: any): DataFrom<T> {
2828
invariant(is(data), DATAERR);
2929
return data;
3030
}

src/helpers.ts

Lines changed: 91 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,122 @@
11
import {
2-
StringValidator,
3-
NumberValidator,
2+
O,
3+
R,
44
BooleanValidator,
5-
Validator,
65
ListValidator,
76
ListofValidator,
7+
NumberValidator,
88
RecordValidator,
99
RecordofValidator,
10+
Schema,
11+
StringValidator,
12+
Validator,
13+
BooleanOptions,
14+
ListOptions,
15+
ListofOptions,
16+
NumberOptions,
17+
RecordOptions,
18+
RecordofOptions,
19+
StringOptions,
1020
} from './validatorTypes';
11-
import { $string, $number, $boolean, $list, $listof, $record, $recordof } from './constants';
12-
13-
type WithoutType<T> = Omit<T, 'type'>;
14-
15-
type Helpers =
16-
| typeof $string
17-
| typeof $number
18-
| typeof $boolean
19-
| typeof $list
20-
| typeof $listof
21-
| typeof $record
22-
| typeof $recordof;
2321

24-
const optional = (v?: boolean) => (typeof v == 'boolean' ? v : false);
25-
26-
const base = <T extends Helpers>(type: T, optional: boolean) => ({ type, optional });
27-
28-
export type StringOptions = Partial<WithoutType<StringValidator>>;
29-
export type NumberOptions = Partial<WithoutType<NumberValidator>>;
30-
export type BooleanOptions = Partial<WithoutType<BooleanValidator>>;
31-
export type ListOptions = { optional?: boolean };
32-
export type ListofOptions = { optional?: boolean };
33-
export type RecordOptions = { optional?: boolean };
34-
export type RecordofOptions = { optional?: boolean };
35-
36-
export function string(opts?: StringOptions): StringValidator {
22+
export function string(config: { optional: true } & Omit<StringOptions, 'type'>): O<StringOptions>;
23+
export function string(config: { optional: false } & Omit<StringOptions, 'type'>): R<StringOptions>;
24+
export function string(
25+
config: { optional?: boolean } & Omit<StringOptions, 'type'>
26+
): StringValidator;
27+
export function string(config: Omit<StringOptions, 'type'>): R<StringOptions>;
28+
export function string(): R<StringOptions>;
29+
export function string(
30+
config?: { optional?: boolean } & Omit<StringOptions, 'type'>
31+
): StringValidator {
3732
return {
38-
...opts,
39-
...base($string, optional(opts?.optional)),
33+
type: 'string',
34+
optional: Boolean(config?.optional),
35+
...config,
4036
};
4137
}
4238

43-
export function boolean(opts?: BooleanOptions): BooleanValidator {
44-
return base($boolean, optional(opts?.optional));
45-
}
46-
47-
export function number(opts?: NumberOptions): NumberValidator {
39+
export function number(config: { optional: true } & Omit<NumberOptions, 'type'>): O<NumberOptions>;
40+
export function number(config: { optional: false } & Omit<NumberOptions, 'type'>): R<NumberOptions>;
41+
export function number(
42+
config: { optional?: boolean } & Omit<NumberOptions, 'type'>
43+
): NumberValidator;
44+
export function number(config: Omit<NumberOptions, 'type'>): R<NumberOptions>;
45+
export function number(): R<NumberOptions>;
46+
export function number(
47+
config?: { optional?: boolean } & Omit<NumberOptions, 'type'>
48+
): NumberValidator {
4849
return {
49-
...opts,
50-
...base($number, optional(opts?.optional)),
50+
type: 'number',
51+
optional: Boolean(config?.optional),
52+
...config,
5153
};
5254
}
5355

54-
export function list<V extends Validator[]>(shape: V, opts?: ListOptions): ListValidator {
56+
export function boolean(config: { optional: true }): O<BooleanOptions>;
57+
export function boolean(config: { optional: false }): R<BooleanOptions>;
58+
export function boolean(): R<BooleanOptions>;
59+
export function boolean(config?: { optional: boolean }): BooleanValidator {
5560
return {
56-
...base($list, optional(opts?.optional)),
57-
shape,
61+
type: 'boolean',
62+
optional: Boolean(config?.optional),
5863
};
5964
}
6065

61-
export function listof<V extends Validator>(of: V, opts?: ListofOptions): ListofValidator {
66+
export function list<T extends Validator[]>(list: T): R<ListOptions<T>>;
67+
export function list<T extends Validator[]>(
68+
list: T,
69+
config: { optional: false }
70+
): R<ListOptions<T>>;
71+
export function list<T extends Validator[]>(list: T, config: { optional: true }): O<ListOptions<T>>;
72+
export function list<T extends Validator[]>(
73+
list: T,
74+
config?: { optional: boolean }
75+
): ListValidator<T> {
76+
return { type: 'list', optional: Boolean(config?.optional), shape: list };
77+
}
78+
79+
export function listof<T extends Validator>(v: T): R<ListofOptions<T>>;
80+
export function listof<T extends Validator>(v: T, config: { optional: false }): R<ListofOptions<T>>;
81+
export function listof<T extends Validator>(v: T, config: { optional: true }): O<ListofOptions<T>>;
82+
export function listof<T extends Validator>(
83+
v: T,
84+
config?: { optional: boolean }
85+
): ListofValidator<T> {
6286
return {
63-
...base($listof, optional(opts?.optional)),
64-
of,
87+
type: 'listof',
88+
optional: Boolean(config?.optional),
89+
of: v,
6590
};
6691
}
6792

68-
export function record<S extends { [key: string]: Validator }>(
69-
shape: S,
70-
opts?: RecordOptions
71-
): RecordValidator {
93+
export function record<T extends Schema>(s: T): R<RecordOptions<T>>;
94+
export function record<T extends Schema>(s: T, config: { optional: false }): R<RecordOptions<T>>;
95+
export function record<T extends Schema>(s: T, config: { optional: true }): O<RecordOptions<T>>;
96+
export function record<T extends Schema>(s: T, config?: { optional: boolean }): RecordValidator<T> {
7297
return {
73-
...base($record, optional(opts?.optional)),
74-
shape,
98+
type: 'record',
99+
optional: Boolean(config?.optional),
100+
shape: s,
75101
};
76102
}
77103

78-
export function recordof<V extends Validator>(of: V, opts?: RecordofOptions): RecordofValidator {
104+
export function recordof<T extends Validator>(v: T): R<RecordofOptions<T>>;
105+
export function recordof<T extends Validator>(
106+
v: T,
107+
config: { optional: false }
108+
): R<RecordofOptions<T>>;
109+
export function recordof<T extends Validator>(
110+
v: T,
111+
config: { optional: true }
112+
): O<RecordofOptions<T>>;
113+
export function recordof<T extends Validator>(
114+
v: T,
115+
config?: { optional: boolean }
116+
): RecordofValidator<T> {
79117
return {
80-
...base($recordof, optional(opts?.optional)),
81-
of,
118+
type: 'recordof',
119+
of: v,
120+
optional: Boolean(config?.optional),
82121
};
83122
}

src/t.ts

Lines changed: 0 additions & 179 deletions
This file was deleted.

0 commit comments

Comments
 (0)