Using TypeScript - Implementing a generic constraint to allow passing a Zod schema result as an argument to a function

I'm in the process of creating a custom controller function to streamline my application. The repetitive task of wrapping try-catch, parsing a zod schema, and merging the request zod schema into a single object is present in all handler functions. Therefore, I am looking to create a wrapper to handle these tasks for each request. My main goal is to maintain type safety throughout the process. Below is the current function I have implemented:

function controller<
  T extends B & Q & P,
  B extends Record<string, any>,
  Q extends Record<string, any>,
  P extends Record<string, any>
>(handlerFn: (args: T) => Promise<any>, zodSchema: { parse: (data: unknown) => { body: B; query: Q; params: P } }) {
  return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
    try {
      const r = zodSchema.parse(req);
      const args = { ...r.body, ...r.params, ...r.query };
      await handlerFn(args);
    } catch (e) {
      next(e);
    }
  };
}

This function takes a handlerFn, which is the core function requiring a parameter T. It also accepts a zodSchema as its second parameter, responsible for validating and returning the body, query, and params fields after parsing to validate the Request object. The input of the handlerFn should be a merge of body, query, and params.

The error message I encounter is:

Argument of type 'B & P & Q' is not assignable to parameter of type 'T'.
  'B & P & Q' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Record<string, any>'.

If anyone has insights or solutions, I would greatly appreciate your help.

Answer №1

The issue arises from the difference between T extends B & Q & P and T = B & Q & P; T might have additional fields, causing B & Q & P to not match with T. To resolve this, consider using B & Q & P directly within the handlerFn:

function controller<
  B extends Record<string, any>,
  Q extends Record<string, any>,
  P extends Record<string, any>
>(
  handlerFn: (args: B & Q & P) => Promise<any>,
  zodSchema: { parse: (data: unknown) => { body: B; query: Q; params: P } }
) {
  return async (req: any, res: any, next: any) => {
    try {
      const r = zodSchema.parse(req);
      const args = { ...r.body, ...r.params, ...r.query };
      await handlerFn(args);
    } catch (e) {
      next(e);
    }
  };
}

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

Tips for parsing a string object in JSON without a preceding double quote

I'm working with an array in my Angular application, for example: searchTerm : any[] In the context of a textbox value like {'state':'tn'}, I'd like to push this to the searchTerm array. Currently, I achieve this by adding t ...

Creating dynamic text bubble to accommodate wrapped text in React using Material-UI (MUI)

I am currently developing a chat application using ReactJS/MUI, and I have encountered an issue with the sizing of the text bubbles. The bubble itself is implemented as a Typography component: <Typography variant="body1" sx={bubbleStyle}> ...

Checking the interceptor response in NestJs testing

I created a basic interceptor that removes a specific field from a response: import { CallHandler, ExecutionContext, Injectable, NestInterceptor, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } ...

Providing a conditional getServerSideProps function

Is there a way to dynamically activate or deactivate the getServerSideProps function using an environment variable? I attempted the following approach: if (process.env.NEXT_PUBLIC_ONOFF === 'true') { export const getServerSideProps: Get ...

How can you personalize the dropdown button in dx-toolbar using DevExtreme?

Currently, I am working with the DevExtreme(v20.1.4) toolbar component within Angular(v8.2.14). However, when implementing a dx-toolbar and specifying locateInMenu="always" for the toolbar items, a dropdown button featuring the dx-icon-overflow i ...

Error: Property 'instance' is undefined and cannot be read

Trying to confirm the functionality of the following method: showSnackbar(): void { if (this.modifiedReferences.length) { const snackbar = this.snackbarService.open({ message: '', type: 'success', durat ...

What is the best way to retrieve the height and width of a device's display in Angular 2 using Typescript

I came across this code snippet. Do you think it's valid? import {Component} from '@angular/core'; import {Platform} from 'ionic-angular'; @Component({...}) export MyApp { constructor(platform: Platform) { platform.ready().then ...

Webpack and TypeScript are throwing an error stating that `$styles` is not defined

I've encountered an issue with my typescript SharePoint spfx solution. After compiling using webpack, my $styles variable becomes undefined even though I am able to use the class names directly. It seems like there might be a configuration problem at ...

How to Turn Off GridToolbarExport Menu in React Mui DataGrid

Can someone assist me in disabling the menu in GridToolbarExport? This is how my MUI Data Grid is set up: <DataGrid localeText={{ toolbarExport: "Export as CSV", }} disableColumnMenu={true} components={{ Toolbar ...

Using Typescript to transform a list into function arguments

My current challenge involves a set of arguments structured like so: const args: FeatureEventArg[] = [ { name: 'username', type: 'string', }, { name: 'message', type: 'string', }, { name ...

What is the reason behind the lack of covariance in an interface when using a type of T[keyof T]?

I'm attempting to enable type covariance so that Type<Cat> can be treated as a Type<Animal>, for instance, treating a list of Cats as a list of Animals. However, using the type T[keyof T] in a method within the Type interface seems to hind ...

Reducing SCSS import path in Angular 7

Creating a component that is deeply nested raises the issue of importing shared .scss files with long paths: @import '../../../app.shared.scss'; This hassle doesn't exist when it comes to .ts files, thanks to the configuration in tsconfig. ...

Exploring Ngu-Carousel in Angular 7: Importing JSON data for a dynamic display

After attempting to import data from JSON and display it using ngu-carousel, I encountered an error. The error shows "length of undefined" Subsequently, when I try to click on the previous/next button, another error appears. This error states "innerHTML ...

Generate TypeScript type definitions for environment variables in my configuration file using code

Imagine I have a configuration file named .env.local: SOME_VAR="this is very secret" SOME_OTHER_VAR="this is not so secret, but needs to be different during tests" Is there a way to create a TypeScript type based on the keys in this fi ...

What is the best method for storing a third-party image in cache?

Running my website, I aim to achieve top-notch performance scores using LightHouse. I have successfully cached all the images I created (Cache-Control: public, max-age=31536000). Unfortunately, third-party website images are not cached. How can I cache t ...

Exploring the world of typed props in Vue.js 3 using TypeScript

Currently, I am attempting to add type hints to my props within a Vue 3 component using the composition API. This is my approach: <script lang="ts"> import FlashInterface from '@/interfaces/FlashInterface'; import { ref } from &a ...

The useForm function from react-hook-form is triggered each time a page is routed in Nextjs

Hey there, I'm just starting out with Next.js (v14) and I'm trying to create a multi-page form using react-hook-form. But I'm encountering an issue where the useForm function is being executed every time, and the defaultValues are being set ...

Is there a potential issue in Next.js 14 when utilizing the "useClient" function alongside conditional rendering in the app/layout.tsx file?

Within my app, there is a Navbar that will only be visible when the route is either "/" or "/teachers". The Navbar will not appear on the dashboard page ("/dashboard"). I achieved this using conditional rendering in the app/layout.tsx file. "use clien ...

Handling onChange events for several typescript <Select> elements

As a non-TS developer, I'm delving into the realm of multiple selects and dropdown menus with Material-UI's select component. Progressing from a basic setup, I successfully implemented a single select but now face a challenge in adding another dr ...

I am encountering an issue regarding the 'endpoint' property within my environment.ts file while working on an Angular 17 project

My goal is to incorporate the property endpoint from my environment.ts file into my service: export const environment = { production: false, endpoint: 'http://localhost:3000/api/cabin/' }; This snippet showcases my service: import {Injectabl ...