Endpoint definitions in Typescript use strict typing

Looking to implement strong typing for API endpoints in a front end client. Here is the current code snippet:

type URI<T extends string> = { url: T }
type QUERY<T> = { query: { [K in keyof T]: T[K] | T[K][] } }
type DATA<T> = { data: Required<{ [K in keyof T]: T[K] }> }
type PATCHDATA<T> = { data: { [K in keyof T]: T[K] } }
type RESPONSE<T> = { response: { [K in keyof T]: T[K] } }
type ENDPOINT = URI<any> & Partial<QUERY<any> & DATA<any> & PATCHDATA<any> & RESPONSE<any>;

type GET<T extends ENDPOINT> = T
type POST<T extends ENDPOINT> = T;

type GET_ACCOUNT = GET<DATA<{}> & URI<'account/:id'> & QUERY<{ userId: string }> & RESPONSE<{}>>
type GET_ACCOUNTS = GET<URI<'accounts'> & QUERY<{ firstName: string, lastName: string }> & RESPONSE<{ id: string, firstname: string, lastName: string }>>

type CREATE_ACCOUNT = POST<QUERY<{}> & URI<'accounts'> & DATA<{}> & RESPONSE<{}>>

type GETS = GET_ACCOUNT | GET_ACCOUNTS
type POSTS = CREATE_ACCOUNT 

type ACCOUNT_ENDPOINTS = GETS | POSTS

Question: How can I trigger a compiler error when attempting to include DATA in a GET endpoint? In GET_ACCOUNT, where it specifies GET<DATA<{}>>, I want a visual indicator (like a red squiggly line) and an error message stating that 'A GET request cannot contain a data property'. I have tried different versions of OMIT<> and EXCLUDE<>

attempts

type GET<T extends Omit<ENDPOINT, 'data'> = T

type GET<T extends Pick<ENDPOINT, Exclude<keyof ENDPOINT, 'data'>>> = T

Answer №1

To prevent DATA from being added, you can intersect it with another type using ENDPOINT:

type GET<T extends ENDPOINT & { data?: "GET requests cannot have DATA" }> = T

The chosen type

{ data?: "GET requests cannot have DATA" }
includes a mildly helpful error message:

The provided error message for not satisfying the constraint is as follows: 'Type 'DATA<{}> & URI<"account/:id"> & QUERY<{ userId: string; }> & RESPONSE<{}>' does not satisfy the constraint 'URI & Partial<QUERY & DATA & PATCHDATA & RESPONSE> & { data?: "GET requests cannot have DATA" | undefined; }'. Type 'DATA<{}> & URI<"account/:id"> & QUERY<{ userId: string; }> & RESPONSE<{}>' is not assignable to type '{ data?: "GET requests cannot have DATA" | undefined; }'. Types of property 'data' are incompatible. Type 'Required<{}>' is not assignable to type '"GET requests cannot have DATA"'.(2344)

Try it out here

Answer №2

Reminiscent of @kelsny's response

To enhance clarity in error messages, I incorporated an intersection and condition type.

type POST<T extends ENDPOINT & T extends QUERY<any> ? "Query not allowed in POST" : unknown > = T

If the following line is executed, it will trigger an error:

type CREATE_ACCOUNT= POST<QUERY<{}> & URI<'accounts'> & DATA<{}> & RESPONSE<{}>>

The type 'QUERY<{}> & URI<"accounts"> & DATA<{}> & RESPONSE<{}>' does not meet the requirement '"Query not allowed in POST"'.ts(2344)

This approach allows for modifying properties within QUERY without impacting POST if necessary.

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

Cypress and Cucumber synergy: Experience automatic page reloads in Cypress with each test scenario in the Describe block

Hey, I'm facing an unusual issue. I have a dialog window with a data-cy attribute added to it. In my cucumber scenarios, I have one like this: Scenario: Users open dialog window When the user clicks on the open dialog button I've written Cypre ...

Separate and handle multiple exceptions of the same instance individually

If I am facing the situation where multiple method calls to another class are possible, each of which could potentially throw the same exception that I am unable to modify, how can I handle each Exception separately without allowing the rest of the functio ...

Exploring the synergies between Angular 5 and atmosphere.js

I've been trying to incorporate atmosphere.js into my angular 5 project. So far, I've followed these steps: npm install --save @types/atmosphere.js npm install --save atmosphere.js In addition, I have set up a service as shown below: import { ...

Retrieve all objects of the selected value using Angular autocomplete when an option is selected

I am currently working with an autocomplete component. I am passing an array of objects and would like to retrieve all item information (option) when an option is selected, not just the field value (option.name). <form class="example-form"> ...

Exploring the capabilities of supertest by testing endpoints in Express with NodeJS

Having trouble setting up a test environment to test my TypeScript Express Node.js endpoints. Here are the packages I've installed: jest ts-jest jest-junit supertest @types/supertest @types/jest This is how my spec file looks like: imp ...

I encounter an error message stating "Cannot read property 'push' of undefined" when trying to add an item to a property within an interface

I have a model defined like this : export interface AddAlbumeModel { name: string; gener: string; signer: string; albumeProfile:any; albumPoster:any; tracks:TrackMode[]; } export interface TrackMode { trackNumber: number; ...

What is the best way to transform a JS const into TSX within a Next.js application?

Exploring the code in a captivating project on GitHub has been quite an adventure. The project, located at https://github.com/MaximeHeckel/linear-vaporwave-react-three-fiber, showcases a 3D next.js application that enables 3D rendering and animation of mes ...

Modifying the appearance of a Component within a NavLink

I'm currently working on a navbar using NavLink from React-Router-Dom. It's fine to use the 'isActive' prop to style the active Link, but I'm stuck on how to style the subelements inside it. For more specific details, please take a ...

The error "Failed to log in. Cannot read property getPackageManager of undefined in Angular 2

Recently, I came across the nativescript-appList Plugin, but unfortunately encountered a runtime error stating "Cannot read property getPackageManager of undefined." My code implementation within the constructor of an Angular2-NativeScript project is as f ...

Removing a dynamic TypeScript object key was successful

In TypeScript, there is a straightforward method to clone an object: const duplicate = {...original} You can also clone and update properties like this: const updatedDuplicate = {...original, property: 'value'} For instance, consider the foll ...

Is there a way to see the countdown timers in Angular 7?

Is there a way for me to view my timer's countdown as it starts? I have attempted to bind it to a variable, but all I see is [object Object]: setTime(time) { if (this.settime >= 5 ) { this.currentTime = time; this.alerttime = thi ...

The issue arises when trying to convert t to a float in TensorFlow within an Angular environment because

I've been working on integrating TensorFlow Facemesh into my Angular application. After importing all the necessary modules and experimenting with different models, I am currently using: import * as tf from '@tensorflow/tfjs'; import * as fa ...

TS2688 Error: Type definition file for 'keyv' is missing

The automated code build process failed last night, even though I did not make any changes related to NPM libraries. The error message I received was: ERROR TS2688: Cannot find type definition file for 'keyv'. The file is in the program because: ...

What is the reason behind the TypeScript HttpClient attempting to interpret a plain text string as JSON data?

When utilizing Angular TypeScript, I have implemented the following approach to send a POST request. This request requires a string parameter and returns a string as the response. sendPostRequest(postData: string): Observable<string> { let header: ...

The items in my array have been replaced with new objects

I am facing an issue with storing objects in an array within a function. Every time the function is called, a new object is received and I want to add it to the existing array without overwriting the previous objects. This way, all the objects can be acc ...

Adjusting the audio length in React/Typescript: A simple guide

I'm currently developing a web app with React and TypeScript. One of the components I created is called SoundEffect, which plays an mp3 file based on the type of sound passed as a prop. interface ISoundEffectProps { soundType: string, // durat ...

What is the process of defining a mongoose method within a schema class when working with nestjs/mongoose?

Is there a way to write a method in a schema class using the nestjs/mongoose package? I'm trying to implement a method as shown below. import { SchemaFactory, Schema, Prop } from '@nestjs/mongoose'; import { Document } from 'mongoose&a ...

The Monaco editor remains hidden until I bring up my developer tools

I'm facing a challenge with my HTML file that includes the integration of a Monaco editor. Upon initially loading the page, the editor appears to be stuck. Strangely, it only starts working when I open my developer tools; if I don't do this, it r ...

How to verify in HTML with Angular whether a variable is undefined

When it comes to the book's ISBN, there are instances where it may not be defined. In those cases, a placeholder image will be loaded. src="http://covers.openlibrary.org/b/isbn/{{book.isbn[0]}}-L.jpg?default=false" ...

What is the best way to implement CSS Float in typescript programming?

For a specific purpose, I am passing CSS Float as props. To achieve this, I have to define it in the following way: type Props = { float: ???? } const Component = ({ float }: Props) => {......} What is the most effective approach to accomplish this? ...