I've been trying to achieve some complex typing for a library I'm working on, but I'm not even sure what I'm trying to achieve is doable with TypesScript.
The library I'm working on is a schema & promise-based form generator built with Vue & TS, that handles for you UI rendering & mounting, validation, etc., and lets you collect data from users in a purely functional way.
Here's a very basic example for you to understand:
const { formData, isCompleted } = await formApi.createForm({
fields: [
{
"key": "email",
"label": "Email address",
"type": "text",
"required": true,
"validators": { "email": isEmail, },
"size": 8
},
{
"key": "password",
"label": "Password",
"type": "password",
"required": true
},
]
})
Here, formData will be equal to: { email: "...", password: "..." }
What I want to achieve is to type FormData to be equal in this case to { email: string, password: string } instead of the { [key: string]: any } type that I currently return.
This is a very simple example, but the library can generate really complex forms, supports deeply nested objects/arrays fields, conditional rendering based on other fields, and a lot of customisation.
EDIT
After seeing replies, I understand that depending on the complexity of the schema, the actual TS implementation will differ a lot.
Considering this, here's a more advanced example that includes a lot of the aspects the lib is able to handle (Live stackblitz example : https://stackblitz.com/edit/vue-xyzwq9?file=src/components/SimpleDependency.vue):
const { isCompleted, formData } = await createForm({
fullScreen: 'true md:false',
title: 'ADVANCED FORM SCHEMA EXAMPLE',
gridSize: 8,
fieldSize: '8 md:4',
fields: [
{
key: 'firstName',
label: 'First name',
type: 'text',
required: true,
},
{
key: 'lastName',
label: 'Last name',
type: 'text',
required: true,
},
{
key: 'email',
label: 'Email',
type: 'text',
size: 8,
},
{
key: 'address',
label: 'Address',
type: 'object',
size: 8,
fields: [
{
key: 'street',
label: 'Street',
type: 'text',
required: true,
},
{
key: 'zipCode',
label: 'ZIP Code',
type: 'text',
required: true,
},
{
key: 'city',
label: 'City',
type: 'text',
required: true,
},
{
key: 'country',
label: 'Country',
type: 'text',
required: true,
},
],
},
{
key: 'skills',
label: 'Dev skills',
type: 'select',
fieldParams: {
multiple: true,
writable: true,
},
options: [
'Vue',
'Angular',
'React',
'ExpressJs',
'NestJs',
'Fastify',
'Typescript',
'C#',
'Swift',
'Go',
'Rust',
].map((value) => ({ label: value, value })),
},
{
key: 'hasProExperiences',
label: 'Has prior professional experiences',
type: 'checkbox',
size: 8,
},
{
key: 'professionalExperiences',
label: 'Professional experiences',
type: 'array',
size: 8,
dependencies: ['hasProExperiences'],
condition: ({ hasProExperiences }) => !!hasProExperiences,
headerTemplate: (_, index) => `JOB ${index + 1}`,
fields: [
{
key: 'company',
label: 'Company',
type: 'text',
required: true,
},
{
key: 'job',
label: 'Job title',
type: 'text',
required: true,
},
{
key: 'period',
label: 'Job period',
type: 'daterange',
required: true,
size: 8,
},
{
key: 'description',
label: 'Job description',
type: 'textarea',
size: 8,
},
],
},
],
});
And from this complex schema, here's the type definition I want to assign to formData in output :
interface ExpectedType {
firstName: string;
lastName: string;
email: string;
address: {
street: string;
zipCode: string;
city: string;
country: string
};
skills: string[] // "multiple" prop set to true on the field, so multiple values selected
hasProExperiences: boolean;
professionalExperiences?: Array<{ // Could be undefined because rendered & included in form state only if 'hasProExperiences' is true
company: string;
job: string;
period: [string, string];
description: string;
}>
}