I have the following code:
const mixed = {
validations: [] as any[],
formattings: [] as any[],
exceptions: [] as any[],
required(message?: string) {
this.validations.push({
name: 'required',
message: message || config.messages.mixed.required,
test: (value: string) => {
return value && value.trim() ? true : false
},
})
return this
},
// other methods that will be reusable
}
const string = () => ({
...mixed,
maxWords(maxWords: number, message?: string) {
type Params = { maxWords: number }
this.validations.push({
name: 'maxWords',
params: { maxWords },
message: message || config.messages.string.maxWords,
test: (value: string, { maxWords }: Params) => {
const wordCount = value.trim().split(' ').length
return wordCount <= maxWords
},
})
return this
},
trim() {
this.formattings.push({
name: 'trim',
format: (value: string) => {
return value.trim()
},
})
return this
},
})
const number = () => ({
...mixed,
positive(message?: string) {
this.validations.push({
name: 'positive',
message: message || config.messages.string.maxWords,
test: (value: string) => {
// make a validation
},
})
return this
},
})
const schema = {
string() {
return string()
},
number() {
return number()
},
// file, date, etc..
}
const form = {
name: schema.string().required().trim().maxWords(3),
age: schema.number().required().positive(),
}
Everything works perfectly in execution, the problem is that I am trying to build a form validation library and I need intellisense working on all methods.
Here's TS Playground so you can test it in real time.
the problem is in the typing of the returns of each function
You can use required(message?: string): this
if you declare the method type in an interface or class. Stripped down example:
interface Validation<T> {
message: string
test: (value: T) => boolean
}
interface FormField<T> {
validations: Validation<T>[];
required(message: string): this;
}
const mixed: FormField<unknown> = {
validations: [],
required(message) {
this.validations.push({
message,
test(value: string) {
return value.trim()
}
})
return this
}
}
interface StringField extends FormField<string> {
maxWords(maxWords: number, message: string): this;
}
const string = (): StringField => ({
...mixed as StringField, // this is a bit ugly, would work better if mixed() was a function or superclass
maxWords(maxWords, message) {
this.validations.push({
message,
test: value => value.trim().split(' ').length <= maxWords,
})
return this
}
})
interface NumberField extends FormField<number> {
positive(message: string): this;
}
const number = (): NumberField => ({
...mixed as NumberField,
positive(message) {
this.validations.push({
message,
test: value => value > 0,
})
return this
},
})
const form = {
name: string().required('missing name').maxWords(3, 'too few words'),
age: number().required('missing age').positive('must be born already'),
}