TS: Deduce explicit typing through method chaining

After overcoming all obstacles, I am finally ready to unleash the first version of my form validation library.

Below is a snippet of the code (simplified for clarity)

interface Validation {
  name: string
  message: string
  params?: Record<string, any>
  test: (value: any, params?: any) => boolean
}

class MixedSchema<T> {
  type!: T
  validations: Validation[] = []

  required(message?: string) {
    this.validations.push({
      name: 'required',
      message: message,
      test: (value: any) => {
        return value === '' ? false : true
      },
    })

    return this
  }

  oneOf(arrayOfValues: any[], message?: string) {
    type Params = { arrayOfValues: any[] }

    this.validations.push({
      name: 'oneOf',
      params: { arrayOfValues },
      message: message,
      test: (value: any, params: Params) => {
        return params.arrayOfValues.includes(value)
      },
    })

    return this
  }
}
// more code...

If everything goes well, I should see the expected result.

https://i.sstatic.net/mA3Xv.png

However, achieving the most accurate typing remains a challenge. Ideally, it should resemble the schema defined for form, like this:

interface HowNeedsToBe {
  email: string
  age: number | undefined
  gender: 'male' | 'female'
  color: 'red' | 'blue' | 'green' | undefined
}

The logic might involve setting undefined in the absence of required, and substituting arguments with T from MixedSchema<T> in case of oneOf. But figuring out how to propagate this back to MixedSchema<T> seems daunting.

I've explored TypeScript's map and generics features extensively, but applying them practically has proven challenging.

You can experiment with the code in the TS playground.

Answer №1

When it comes to narrowing types in TypeScript, the required() and oneOf() methods are key players. The challenge lies in ensuring that the type is correctly narrowed, which might require using type assertions to satisfy the compiler. For instance, calling required() on a MixedSchema<T> should ideally return a

MixedSchema<Exclude<T, undefined>>
, effectively removing any instances of undefined from the union members of T. Similarly, oneOf() needs to be generic in the element type U within arrayOfValues, returning a
MixedSchema<U | Extract<undefined, T>>
.

In practice, implementing this concept involves some complexity, especially when subclassing MixedSchema. Maintaining the specific type after modification, as seen with StringSchema and NumberSchema, requires careful handling to prevent unintentional widening back to MixedSchema.

The ideal solution would involve higher-kinded types or simulating them through specified type mappings like:

type Specify<C extends MixedSchema<any>, T> = 
    C extends NumberSchema<any> ? NumberSchema<Extract<T, number | undefined>> :
    C extends StringSchema<any> ? StringSchema<Extract<T, string | undefined>> :
    MixedSchema<T>;

This methodology allows for accurate inference of field types while defining a new implementation of MixedSchema. Subsequent verification tests ensure the expected behavior in subclasses.

By following these strategies, you can ensure that your inferred types align with your expectations, allowing for seamless data validation across various fields.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Passing props to another component using the <Link> element in React Router

I'm working on a project where I need to display search results using an array. When a user clicks on an item, I want to pass that item as props rather than as parameters. Below is the code snippet: { this.props.results.map((result) => { ...

Enhance user experience by implementing an autocomplete feature for a text input field

Can you name the process in which a form is automatically filled using database information without needing to refresh the page? One common example of this technique is seen on platforms like Google or Facebook, where they predict friends you may be searc ...

Go to a different webpage containing HTML and update the image source on that particular page

I am facing an issue with my html page which contains multiple links to another page. I need to dynamically change the image references on the landing page based on the link the user clicks. The challenge here is that the link is inside an iframe and trigg ...

The drop-down menu fails to appear when I move my cursor over it

#menu { overflow: hidden; background: #202020; } #menu ul { margin: 0px 0px 0px 0px; padding: 0px 0px; list-style: none; line-height: normal; text-align: center; } #menu li { display: inline-block; } #menu a { display: block; position: relative; padding ...

Tell webpack to exclude a specific import

Currently, I am in the process of developing a desktop application using ElectronJS and ReactJS. To bundle the renderer process that utilizes JSX, I have opted to use webpack. An issue arises when attempting to import anything from electron into the rend ...

"Interactive table feature allowing for scrolling, with anchored headers and columns, as well as the option to fix

I'm in need of a table that has certain requirements, as shown in the image. https://i.stack.imgur.com/Z6DMx.png The specific criteria include: The header row (initially blurred) should remain fixed when scrolling down the table The first column s ...

Is it possible to dynamically update the data being sent along with a partial when rendering a Handlebars partial in a Node.js, MongoDB, Express application without the need to reload the entire webpage?

Is it possible to dynamically update data sent WITH THE PARTIAL view in a rendering scenario using hbs partial (nodejs, mogodb, express) without needing to reload the entire webpage? For example, if I have a post route for comments that queries the databa ...

Upon running `npm start`, an unexpected token error arises in the file originating from a local

After developing my first-app with the help of create-react-app, I incorporated some components from Material-UI. Everything was running smoothly when I launched it using npm start. Upon completion, I decided to extract the nice-component into its own fol ...

Developing a bespoke React Typescript button with a custom design and implementing an onClick event function

Currently, I am in the process of developing a custom button component for a React Typescript project utilizing React Hooks and Styled components. // Button.tsx import React, { MouseEvent } from "react"; import styled from "styled-components"; export int ...

Rotating and scaling an image simultaneously using CSS3

I am feeling very puzzled. Why am I unable to simultaneously scale and rotate? Despite my attempts, it seems to be not working: .rotate-img{ -webkit-transform:scale(2,2); -webkit-transform:rotate(90deg); margin-left:20%; margin-top:10%; } ...

The Firebase JQuery .on method is incrementally updating individual values in an array instead of updating them all simultaneously

I am trying to update the values of the orders placed by users on the Corporate's page without a refresh. I have implemented the jQuery .on method for this purpose. However, the values are being returned one by one from the array created for the order ...

Pressing a button within an HTML table to retrieve the corresponding value in that row

How can I click a button inside an HTML table, get the value on the same row and pass it to an input field called FullName? Thanks for your help! <td><?php echo $r['signatoryname'] ?></td> <td ...

What is the location where Three.js establishes the default shaders for materials?

I've been attempting to locate the exact location where the fragment and vertex shaders are being assigned after creating a Three.js material, but haven't had much success. Using the ParticleSystemMaterial, I have material = new THREE.ParticleSy ...

Utilize the technique on the designated variable

I need to modify a variable to use a method in the following way; var method = ".wrap"; jQuery('.container')+[method]+("<div>Hello World</div>"); The desired outcome is; jQuery('.container').wrap("<div>Hello Worl ...

Find the length of time in Typescript (measured in hours, minutes, and seconds)

Trying to calculate the duration between two dates in TypeScript (Angular): 2021-11-19 21:59:59 and 2021-11-19 22:00:18 let startDate: Date = new Date(start); let endDate: Date = new Date(end); if(end != null) { let duration = new Date(endDate.getT ...

Is JQuery the ultimate solution for creating a dynamic multi-language website?

Embarking on a new project that requires support for multiple languages. My plan is to create a jQuery/AJAX based application with all the code in jQuery, simply calling JSONs for data. What would be the most effective approach for implementing multi-lan ...

What is the process for generating a fresh 2D array from each layer within a provided nested array?

Input: [1,2,[3,4,[5,6]]] Output: [[1,2],[3,4],[5,6]] Provided below is a solution to the given problem: function convert(a,res=[]) { const group = (arr) => { res.push(arr.slice(0,2)); arr.map((v) => Array.isArray(v) && group(v)); ...

Personalize the iOS smartbanner

We have a plan to incorporate smart banners into our app. Is there a way to personalize the smart banner so that the close button is hidden and cannot be closed by the user? I attempted to use a jQuery plugin (https://github.com/jasny/jquery.smartbanner) ...

The function self.ctx.$scope.$apply is missing or undefined

I've been working on a custom widget for Thingboard PE that calls an API endpoint and displays the results in a table format. I keep encountering the error message "self.ctx.$scope.$apply is not a function" and despite my efforts, I haven't been ...

Unable to retrieve props from server-side page to client-side component in a Next.js application router

Currently, I am utilizing app router alongside Next.js version 13.5. Within my /dashboard page (which is a server component), there is an ApiKeyOptions client component embedded. However, when attempting to pass props from the dashboard page to the ApiKeyO ...