Skip to content

Commit 94523e9

Browse files
committed
refactor: update readme & enhanced type informations
1 parent 5478feb commit 94523e9

4 files changed

Lines changed: 83 additions & 45 deletions

File tree

README.md

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ export const Person = createSchema<IPerson>({
5353

5454
## Schema
5555

56-
When you create a schema you will get a nice API to handle multiple use-cases in the client and the server.
56+
When you create a schema, you will get a nice API to handle multiple use-cases in the client and the server.
5757

58-
- `is(data: any): boolean` check wether data is valid
58+
- `is(data: any): boolean` check if the data is valid
5959
- `validate(data: any): Errors` errors returned has the same shape as the schema you defined (does not throw)
60-
- `produce(data: any): data` throws an error when data has errors otherwise returns data
61-
- `embed(config?: { optiona: boolean })` embeds the schema in other schemas
60+
- `produce(data: any): data` throws an error when the data is invalid. otherwise, it returns data
61+
- `embed(config?: { optional: boolean })` embeds the schema in other schemas
6262
- `traverse(visitor, data?, eager?)` (advanced) see usage below.
6363

6464
Continuing from the previous example:
@@ -70,7 +70,7 @@ Person.is({ name: 'john', age: 42, email: '[email protected]' }); // true
7070
Person.validate({}); // { name: 'invalid-type', age: 'invalid-type', email: 'invalid-type' }
7171
Person.validate({ name: 'john', age: 42, email: '[email protected]' }); // null
7272

73-
Person.produce(undefined); // throws error with the same shape as the schema
73+
Person.produce(undefined); // throws an error with the same shape as the schema
7474

7575
// embedding the person schema
7676
const GroupOfPeople = createSchema({
@@ -125,14 +125,40 @@ Check out the full validators API below:
125125
| | | options(optional): Object |
126126
| | | - `optional` boolean default to false |
127127

128+
### Custom validators
129+
130+
You can use validators from `_` as building blocks for your custom validator:
131+
132+
```js
133+
const alphaNumeric = validatorOpts =>
134+
_.string({
135+
pattern: [/^[a-zA-Z0-9]*$/, 'only-letters-and-number'],
136+
...validatorOpts,
137+
});
138+
139+
const email = validatorOpts =>
140+
_.string({
141+
pattern: [/^[^@]+@[^@]+\.[^@]+$/, 'invalid-email'],
142+
...validatorOpts,
143+
});
144+
145+
const Person = createSchema({
146+
// ...
147+
username: alphaNumeric({ maxLength: [20, 'username-too-long'] }),
148+
email: email(),
149+
alt_email: email({ optional: true }),
150+
// ...
151+
});
152+
```
153+
128154
## Advanced usage
129155

130-
In addition to validating data, you can also reuse your schema in other areas, like creating forms and TypeScript types.
156+
In addition to validating data, you can also reuse your schema in other areas, like creating forms UI for example,
131157
`traverse` function come-in handy to help you achieve that.
132158

133159
### Example
134160

135-
In this example we will transform a schema to create meta-data to be used to create form UI elements.
161+
In this example, we will transform a schema to create meta-data to be used to create form UI elements.
136162

137163
```js
138164
const User = createSchema({
@@ -166,14 +192,14 @@ console.log(form_ui); /*
166192

167193
### How to traverse
168194

169-
The return type of your visitor is important, and there is some few considerations:
195+
The return type of your visitor is important, and there are a few considerations:
170196

171-
Returning `null` from a visitor signals to ignore this node from the end result, with the exception of:
197+
Returning `null` from visitor signals to ignore this node from the result, with the exception:
172198
`record | recordof | list | listof`, returning `null` signals to continue down recursively.
173199

174-
This means, to return something from `record` visitor you will need to visit its children recursively.
200+
So to return something from `record` visitor you will need to visit its children recursively.
175201

176-
_Note_: in most cases defining only the primitive visitors is enough, otherwise look at the next example.
202+
_Note_: in most cases defining only the primitive visitors is enough.
177203

178204
Continuing from the previous `User` Example
179205

@@ -183,30 +209,25 @@ Say We need this structure:
183209
{
184210
profile: {
185211
type: 'container',
186-
children: {
187-
username: { type: 'text', label: 'username' },
188-
email: { type: 'text', label: 'email' },
189-
age: { type: 'number', label: 'age' }
190-
}
212+
children: [
213+
{ type: 'text', label: 'username' },
214+
{ type: 'text', label: 'email' },
215+
{ type: 'number', label: 'age' }
216+
]
191217
}
192218
}
193219
*/
194-
const customTraverse = (key: string, validator: Validator) => {
220+
const customTraverse = (key, validator) => {
195221
const type = validator.type;
196222

197-
if (type == 'string') {
198-
return { type: key == 'email' ? key : 'text', label: key };
199-
} else if (type == 'number') {
200-
return { type: 'number', label: key };
201-
} else if (type == 'record') {
202-
const children = Object.entries(validator.shape).reduce((acc, [shapeKey, shapeValidator]) => {
203-
acc[shapeKey] = customTraverse(shapeKey, shapeValidator); // visit children
204-
return acc;
205-
}, {});
206-
return { type: 'container', children };
207-
} else {
208-
return null;
209-
}
223+
if (type == 'string') return { type: key == 'email' ? key : 'text', label: key };
224+
if (type == 'number') return { type: 'number', label: key };
225+
if (type == 'record')
226+
return {
227+
type: 'container',
228+
children: Object.entries(validator.shape).map(entry => customTraverse(...entry)),
229+
};
230+
return null;
210231
};
211232

212233
const form_ui = User.traverse({

src/helpers.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,54 @@ const optional = (v?: boolean) => (typeof v == 'boolean' ? v : false);
2525

2626
const base = <T extends Helpers>(type: T, optional: boolean) => ({ type, optional });
2727

28-
export function string(opts?: Partial<WithoutType<StringValidator>>): StringValidator {
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 {
2937
return {
3038
...opts,
3139
...base($string, optional(opts?.optional)),
3240
};
3341
}
3442

35-
export function boolean(opts?: Partial<WithoutType<BooleanValidator>>): BooleanValidator {
43+
export function boolean(opts?: BooleanOptions): BooleanValidator {
3644
return base($boolean, optional(opts?.optional));
3745
}
38-
export function number(opts?: Partial<WithoutType<NumberValidator>>): NumberValidator {
46+
47+
export function number(opts?: NumberOptions): NumberValidator {
3948
return {
4049
...opts,
4150
...base($number, optional(opts?.optional)),
4251
};
4352
}
44-
export function list(shape: Validator[], opts?: { optional?: boolean }): ListValidator {
53+
54+
export function list<V extends Validator>(shape: V[], opts?: ListOptions): ListValidator {
4555
return {
4656
...base($list, optional(opts?.optional)),
4757
shape,
4858
};
4959
}
50-
export function listof(of: Validator, opts?: { optional?: boolean }): ListofValidator {
60+
61+
export function listof(of: Validator, opts?: ListofOptions): ListofValidator {
5162
return {
5263
...base($listof, optional(opts?.optional)),
5364
of,
5465
};
5566
}
56-
export function record(
57-
shape: { [key: string]: Validator },
58-
opts?: { optional?: boolean }
59-
): RecordValidator {
67+
68+
export function record(shape: { [key: string]: Validator }, opts?: RecordOptions): RecordValidator {
6069
return {
6170
...base($record, optional(opts?.optional)),
6271
shape,
6372
};
6473
}
65-
export function recordof(of: Validator, opts?: { optional?: boolean }): RecordofValidator {
74+
75+
export function recordof(of: Validator, opts?: RecordofOptions): RecordofValidator {
6676
return {
6777
...base($recordof, optional(opts?.optional)),
6878
of,

src/traverse.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type IVisitor<T> = (
3535
key: string,
3636
v: T,
3737
value: any
38-
) => string | null | Record<string, any>;
38+
) => string | null | any[] | Record<string, any>;
3939

4040
export interface Visitor {
4141
string?: IVisitor<StringValidator>;
@@ -108,14 +108,22 @@ function enter(
108108
return ObjectKeys(result).length > 0 ? result : null;
109109
}
110110

111+
type Schema = { [key: string]: Validator };
112+
type SchemaFrom<T> = {
113+
[K in keyof T]: Validator;
114+
};
115+
type FromTraverse<S extends Schema> = {
116+
[K in keyof S]: ReturnType<VisitorFunction>;
117+
};
118+
111119
export function traverse<T>(
112-
schema: { [K in keyof T]: Validator },
120+
schema: SchemaFrom<T>,
113121
visitor: Visitor,
114122
data: T,
115123
eager = false
116-
) {
124+
): Partial<FromTraverse<SchemaFrom<T>>> {
117125
const schemaKeys = ObjectKeys(schema) as (keyof T)[];
118-
const parent = {} as Record<keyof T, any>;
126+
const parent = {} as Partial<FromTraverse<SchemaFrom<T>>>;
119127
for (let i = 0; i < schemaKeys.length; i++) {
120128
const schemaKey = schemaKeys[i];
121129
const validator = schema[schemaKey];

src/validatorTypes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ interface StringOptions {
99
minLength?: [number, string];
1010
maxLength?: [number, string];
1111
pattern?: [RegExp, string];
12-
oneOf?: string[];
1312
}
1413

1514
interface NumberOptions {

0 commit comments

Comments
 (0)