adria-forms
Advanced tools
Comparing version 0.2.4 to 0.3.0
declare type MaybePromise<T> = T | Promise<T>; | ||
declare type FormDataMap<T = any> = Map<T, any | null>; | ||
declare type Validate<T> = (value: any | null, form: FormDataMap<T>) => MaybePromise<any | void>; | ||
declare type ParseFunction<T> = (value: any | null, form: FormDataMap<T>) => MaybePromise<any>; | ||
declare type Not<T, A> = T extends A ? never : T; | ||
declare type FieldRecord<T> = Record<string, Validate<T>>; | ||
declare type ErrorRecord<F extends FieldRecord<string> | FieldRecord<never>> = { | ||
declare type FieldRecord<T> = Record<string, ParseFunction<T>>; | ||
declare type ResultRecord<F extends FieldRecord<string> | FieldRecord<never>> = { | ||
[FieldName in keyof F]: Awaited<ReturnType<F[FieldName]>>; | ||
@@ -12,5 +12,11 @@ }; | ||
constructor(fields?: Fields); | ||
field: <FieldName extends string, V extends Validate<FieldName | keyof Fields>>(fieldName: Not<FieldName, keyof Fields>, validate: V) => Form<Fields & Record<FieldName, V>>; | ||
validate: (formData: FormData | Record<string, string | number>) => Promise<ErrorRecord<Fields> | null>; | ||
field: <FieldName extends string, PF extends ParseFunction<FieldName | keyof Fields>>(fieldName: Not<FieldName, keyof Fields>, parseFunction: PF) => Form<Fields & Record<FieldName, PF>>; | ||
parse: (formData: FormData | Record<string, string | number>) => Promise<{ | ||
errors: null; | ||
data: ResultRecord<Fields>; | ||
} | { | ||
errors: { [k in keyof Fields]?: unknown; }; | ||
data: null; | ||
}>; | ||
} | ||
export {}; |
32
form.js
@@ -6,7 +6,7 @@ export class Form { | ||
} | ||
field = (fieldName, validate) => { | ||
this.fields[fieldName] = validate; | ||
field = (fieldName, parseFunction) => { | ||
this.fields[fieldName] = parseFunction; | ||
return this; | ||
}; | ||
validate = async (formData) => { | ||
parse = async (formData) => { | ||
const errors = {}; | ||
@@ -16,13 +16,25 @@ const formDataMap = formData instanceof FormData | ||
: new Map(Object.entries(formData)); | ||
for (const [fieldName, validate] of Object.entries(this.fields)) { | ||
const data = {}; | ||
for (const [fieldName, parse] of Object.entries(this.fields)) { | ||
const formValue = formDataMap.get(fieldName); | ||
const result = await validate(formValue, formDataMap); | ||
if (result === undefined) | ||
continue; | ||
errors[fieldName] = result; | ||
try { | ||
const parseResult = await parse(formValue, formDataMap); | ||
if (parseResult === undefined) | ||
continue; | ||
data[fieldName] = parseResult; | ||
} | ||
catch (e) { | ||
errors[fieldName] = e; | ||
} | ||
} | ||
if (Object.keys(errors).length > 0) | ||
return errors; | ||
return null; | ||
return { | ||
errors: errors, | ||
data: null, | ||
}; | ||
return { | ||
errors: null, | ||
data: data, | ||
}; | ||
}; | ||
} |
{ | ||
"name": "adria-forms", | ||
"version": "0.2.4", | ||
"version": "0.3.0", | ||
"description": "A super simple form validation library", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
# Adria | ||
A super simple form validation library, with autocomplete and value/type checking using the power of TypeScript. | ||
A super simple form validation and parsing library written in TypeScript. Works both in the client and server. | ||
```bash | ||
npm i adria-forms | ||
yarn add adria-forms | ||
pnpm add adria-forms | ||
``` | ||
@@ -17,6 +19,6 @@ | ||
const form = new Form().field("username", (value) => { | ||
if (!value) return "Please enter your username"; | ||
if (typeof value !== "string") return "Invalid input"; | ||
if (value.length < 4) return "Username must be at least 4 characters long"; | ||
return; // success | ||
if (!value) throw "Please enter your username"; | ||
if (typeof value !== "string") throw "Invalid input"; | ||
if (value.length < 4) throw "Username must be at least 4 characters long"; | ||
return value; | ||
}); | ||
@@ -26,7 +28,6 @@ | ||
const errors = await form.validate(formData); | ||
/* | ||
usernameError will be typed as "Please enter..." or "Username must be..." | ||
*/ | ||
const usernameError = errors?.username; // autocomplete | ||
const { data, errors } = await form.parse(formData); | ||
// intellisense | ||
const usernameError = errors?.username; | ||
const { username } = data; | ||
``` | ||
@@ -36,17 +37,19 @@ | ||
### .field() | ||
## `Form` | ||
Creates a new field. `fieldName` cannot be an existing field name, and `validate` can be a synchronous or asynchronous function. A `void` return from `validate` will tell Adria the value has passed the validation. | ||
#### `field()` | ||
Creates a new field. `fieldName` cannot be an existing field name, and `parse` can be a synchronous or asynchronous function. The return value will be mapped to `data[fieldName]` of the return type of `parse()`. | ||
```ts | ||
const field: ( | ||
fieldName: string, | ||
validate: ( | ||
parse: ( | ||
value: null | FormDataEntryValue, | ||
formData: Map<string, FormDataEntryValue | null> | ||
) => MaybePromise<void | any> | ||
) => this; | ||
) => MaybePromise<any> | ||
) => Form; | ||
``` | ||
#### Example | ||
##### Example | ||
@@ -57,7 +60,7 @@ ```ts | ||
if (!value) | ||
return { | ||
throw { | ||
code: 0, | ||
message: "empty input", | ||
}; | ||
return; // success | ||
return value; // success | ||
}) | ||
@@ -74,10 +77,11 @@ .field("password", (_, formData) => { | ||
### .validate() | ||
#### `parse()` | ||
Validates the form data. Will only check fields defined with `.field()`. Will return `null` if the form data is valid or a `fieldName:errorMessage` record if not. | ||
Validates and parses the form data. Will only check fields defined with `.field()`. Either `errors` or `data` will be `null`, but not both. | ||
```ts | ||
const validate: ( | ||
formData: FormData | Record<any, any> | ||
) => Promise<Record<string, any> | null>; | ||
const parse: (formData: FormData | Record<any, any>) => Promise<{ | ||
errors: Record<string, any> | null; // <fieldName, error> | ||
data: Record<string, any> | null; // <fieldName, parseResult> | ||
}>; | ||
``` | ||
@@ -89,32 +93,18 @@ | ||
const form = new Form() | ||
.field("username", () => { | ||
return "error"; | ||
.field("username", (val) => { | ||
return val; | ||
}) | ||
.field("password", () => { | ||
return { | ||
code: 0, | ||
}; | ||
.field("password", (val) => { | ||
if (invalid) throw Error; | ||
return val; | ||
}); | ||
const errors = await form.validate(formData); | ||
const { errors, data } = await form.parse(formData); | ||
const userNameError: "fail" = errors.username; // autocomplete username, password | ||
const randomError = errors.random; // TS will yell at you since field random does not exist | ||
const passwordErrorCode: number = errors.password.code; // since password can return an object, code will be typed as number and not 0 | ||
``` | ||
## TypeScript | ||
In the previous example (`validate()`), errors will only be typed with a value when the validate function returns a string/number. We can fix this by typing the return value of the validate function `as const`. | ||
```ts | ||
const form = new Form().field("password", () => { | ||
return { | ||
code: 0, | ||
} as const; | ||
}); | ||
const errors = await form.validate(formData as FormData); | ||
const passwordErrorCode: number = errors.password.code; // typed as 0, and not number as before | ||
``` | ||
if (errors) { | ||
const usernameError: undefined | unknown = errors.username; | ||
const passwordError: undefined | unknown = errors.password; | ||
} else { | ||
const { username, password } = data; | ||
} | ||
``` |
@@ -1,6 +0,6 @@ | ||
export declare const pattern: <R extends RegExp, E extends unknown>(regexp: R, error: E) => (value: any) => E | undefined; | ||
export declare const max: <Max extends number, E extends unknown>(maxNum: Max, error: E) => (value: any) => E | undefined; | ||
export declare const min: <Min extends number, E extends unknown>(minNum: Min, error: E) => (value: any) => E | undefined; | ||
export declare const maxLength: <Max extends number, E extends unknown>(maxNum: Max, error: E) => (value: any) => E | undefined; | ||
export declare const minLength: <Min extends number, E extends unknown>(minNum: Min, error: E) => (value: any) => E | undefined; | ||
export declare const required: <E extends unknown>(error: E) => (value: any) => E | undefined; | ||
export declare const pattern: (regexp: RegExp, error: any) => (value: any) => string; | ||
export declare const max: (maxNum: number, error: any) => (value: any) => number; | ||
export declare const min: (minNum: number, error: any) => (value: any) => number; | ||
export declare const maxLength: (maxNum: number, error: any) => (value: any) => string; | ||
export declare const minLength: (minNum: number, error: any) => (value: any) => string; | ||
export declare const required: (error: any) => (value: any) => string; |
49
utils.js
export const pattern = (regexp, error) => { | ||
return (value) => { | ||
if (typeof value !== "string") | ||
return error; | ||
throw error; | ||
if (!regexp.test(value)) | ||
return error; | ||
throw error; | ||
return value; | ||
}; | ||
@@ -11,6 +12,8 @@ }; | ||
return (value) => { | ||
if (typeof value !== "string" && typeof value !== "number") | ||
return error; | ||
if (Number(value) > maxNum) | ||
return error; | ||
const parsedValue = Number(value); | ||
if (isNaN(parsedValue)) | ||
throw error; | ||
if (parsedValue > maxNum) | ||
throw error; | ||
return parsedValue; | ||
}; | ||
@@ -20,6 +23,8 @@ }; | ||
return (value) => { | ||
if (typeof value !== "string" && typeof value !== "number") | ||
return error; | ||
if (Number(value) < minNum) | ||
return error; | ||
const parsedValue = Number(value); | ||
if (isNaN(parsedValue)) | ||
throw error; | ||
if (parsedValue < minNum) | ||
throw error; | ||
return parsedValue; | ||
}; | ||
@@ -30,5 +35,7 @@ }; | ||
if (typeof value !== "string" && typeof value !== "number") | ||
return error; | ||
if (value.toString().length > maxNum) | ||
return error; | ||
throw error; | ||
const parsedValue = value.toString(); | ||
if (parsedValue.length > maxNum) | ||
throw error; | ||
return parsedValue; | ||
}; | ||
@@ -39,5 +46,7 @@ }; | ||
if (typeof value !== "string" && typeof value !== "number") | ||
return error; | ||
if (value.toString().length < minNum) | ||
return error; | ||
throw error; | ||
const parsedValue = value.toString(); | ||
if (parsedValue.length < minNum) | ||
throw error; | ||
return parsedValue; | ||
}; | ||
@@ -48,6 +57,8 @@ }; | ||
if (typeof value !== "string" && typeof value !== "number") | ||
return error; | ||
if (value.toString().length < 1) | ||
return error; | ||
throw error; | ||
const parsedValue = value.toString(); | ||
if (parsedValue.length < 1) | ||
throw error; | ||
return parsedValue; | ||
}; | ||
}; |
128
7973
104