The process of declaring class properties in TypeScript

Here is a custom decorator for Nest.Js:

import { SetMetadata } from '@nestjs/common';
import { PermissionPolicy } from '../permission.policy';

type ClassThatExtends<TClass = any> = new (...args) => TClass;

// Check if user has provided permission to access this route
export const Acl = <
  // this means expecting the class not an instance,
  TPolicyClass extends ClassThatExtends<PermissionPolicy>,
  TActions extends (keyof InstanceType<TPolicyClass>)[],
>(
  policyClass: TPolicyClass,
  actions: TActions,
) => {
  const policyInstance = new policyClass();

  const requiredPermissions = actions.reduce((acc, actionName) => {
    return [...acc, ...policyInstance[actionName]];
    // >> TS2536: Type 'keyof InstanceType ' cannot be used to index type 'PermissionPolicy'.
  }, [] as string[]);

  return SetMetadata('aclPermissions', requiredPermissions);
};

What issue is present here and how can it be resolved?

Currently, I am addressing the error by suppressing it using the following approach, although I feel that there might be a better solution.

return [...acc, ...policyInstance[actionName as keyof PermissionPolicy]];

Expected usage example:

@Acl(AuthPolicy, ['update']) // The property 'update' is part of AuthPpolicy

Answer №1

It's not completely clear why the code is failing, but simplifying the generics seems to resolve the issue.

The complexity of the generics may be a contributing factor to the problem. For example,

Acl<typeof AuthPolicy, ['base']>(...)

This explicit specification can make it harder for the compiler to ensure logical pairing of types.

I don't have a definitive answer as to why this is failing.


However, it's unnecessary to make the class constructor generic. The specific constructor doesn't matter as long as it creates an instance derived from PermissionPolicy.

Similarly, there's no need to make TActions generic since it's not utilized in any way.

Therefore, you can simplify the code to:

export const Acl = <TPolicy extends PermissionPolicy>(
  policyClass: new () => TPolicy,
  actions: (keyof TPolicy)[],
) => {
  const policyInstance = new policyClass();

  const requiredPermissions = actions.reduce<string[]>((acc, actionName) => {
    [...acc, ...policyInstance[actionName]];
    // Type 'TPolicy[keyof TPolicy]' must have a '[Symbol.iterator]()' method that returns an iterator.(2488)
  }, []);

  return SetMetadata('aclPermissions', requiredPermissions);
};

This introduces a different error, indicating that the compiler cannot ascertain whether policyInstance[actionName] is an array. A check can be added to address this.

export const Acl = <TPolicy extends PermissionPolicy>(
  policyClass: new () => TPolicy,
  actions: (keyof TPolicy)[],
) => {
  const policyInstance = new policyClass();

  const requiredPermissions = actions.reduce<string[]>((acc, actionName) => {
    const value = policyInstance[actionName]

    if (Array.isArray(value)) {
      return [...acc, ...value];
    } else {
      return acc
    }
  }, []);

  return SetMetadata('aclPermissions', requiredPermissions);
};

You could also utilize a utility type to filter keys with a type of string[]:

type StringArrayKeys<T> = {
  [K in keyof T]:
    T[K] extends string[] ? K : never
}[keyof T]

export const Acl = <TPolicy extends PermissionPolicy>(
  policyClass: new () => TPolicy,
  actions: StringArrayKeys<TPolicy>[],
) => { /* ... */ }

This ensures that the key corresponds to a string array. However, runtime checks are still necessary for type validation.


Simplifying your types can often resolve complex TypeScript issues by reducing the number of hoops the type checker needs to navigate.

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

What strategies can be implemented to avoid re-rendering in Angular 6 when the window is resized or loses focus?

I am currently working with a component in Angular 6.0.8 that consists of only an iframe element. Here is the code in page.component.html: <iframe [src]="url"> The logic for setting the URL is handled in page.component.ts: ngOnInit() { this.u ...

A Typescript tutorial on using the video HTML element

I have been attempting to identify the presence of audio in a video loaded within an HTML video element that is compatible with both Mozilla and Chrome. To accomplish this task, I am utilizing the React Player package. Below is a snippet of the code for th ...

Converting PHP code to Typescript

Currently, I am working on developing a function for firebase that will trigger a POST request to call a specific URL. While I have successfully implemented GET requests, tackling the POST method has proven to be more challenging. I have some sample code ...

Exploring the NestJS framework using mongoose schema, interfaces, and DTOs: A deep dive

As a newcomer to nestJS and mongoDB, I find myself questioning the need to declare DTO, schema, and interface for every collection we aim to store in our mongoDB. For example, I have a collection (unfortunately named collection) and this is the DTO I' ...

The error message "The element 'router-outlet' is unrecognized during the execution of ng build --prod" appears

I encountered a specific error message when running ng build --prod, although the regular ng build command works without issue. Error: src/app/app.component.html:1:1 - error NG8001: 'router-outlet' is not recognized as an element: 1. If 'rou ...

Using Angular to create a dynamic form with looping inputs that reactively responds to user

I need to implement reactive form validation for a form that has dynamic inputs created through looping data: This is what my form builder setup would be like : constructor(private formBuilder: FormBuilder) { this.userForm = this.formBuilder.group({ ...

Angular TS2564 Error: Attempting to access an uninitialized property 'formGroup'

userForm: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.setupForm(); } setupForm() { this.userForm = this.formBuilder.group({ 'username': ['', Validators.required], 'pa ...

Challenge 11: Transforming a Tuple into an Object

Although I am relatively new to strong typing, I have been immersed in TypeScript for a bit. To enhance my skills in strong typing and gain a better grasp of the type system in TypeScript, I've decided to tackle the Type Challenges. One thing that ha ...

I'm currently facing issues with the API not returning the list of users to my Angular client application. As I am still in the early stages of learning Angular, I am

Upon inspecting the Network tab, I noticed that one of the users is highlighted in red indicating an error. The console also displays an error 401, indicating unauthorized access. Below is the code snippet from the memberService.ts and member-list-componen ...

The most efficient method for distributing code between TypeScript, nodejs, and JavaScript

I am looking to create a mono repository that includes the following elements: shared: a collection of TypeScript classes that are universally applicable WebClient: a react web application in JavaScript (which requires utilizing code from the shared folde ...

Verify the rendering process in the ForwardRef's render method

I have implemented the code exactly as provided in the example from https://material-ui.com/components/bottom-navigation/: // code in app.tsx: import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Bo ...

Discovering the bottom scroll position in an Angular application

I am working on implementing two buttons on an Angular web page that allow the user to quickly scroll to the top and bottom of the page. However, I want to address a scenario where if the user is already at the very top of the page, the "move up" button sh ...

How to attach an event listener to an input element using Angular

I am looking to add a listener to an input element that will be triggered every time the user changes the input values. The goal is to display the current values chosen by the user. Example HTML template: <div id="idDoseLabel1" class="da ...

Ways to troubleshoot opencv.js generating random values when applying cv.threshold

Whenever I choose an image and use cv.threshold, it outputs a strange number 6620912 The number seems to change at times https://i.sstatic.net/Tp9LP.png 6620912 Promise.catch (async) (anonymous) @ App.tsx:49 commitHookEffectListMount @ react-dom_client ...

Issues have arisen with compiling TypeScript due to the absence of the 'tsc command not found' error

Attempting to set up TypeScript, I ran the command: npm install -g typescript After running tsc --version, it returned 'tsc command not found'. Despite trying various solutions from Stack Overflow, GitHub, and other sources, I am unable to reso ...

typescript error: passing an 'any' type argument to a 'never' type parameter is not allowed

Encountering an error with newUser this.userObj.assigned_to.push(newUser);, receiving argument of type 'any' is not assignable to parameter of type 'never' typescript solution Looking for a way to declare newUser to resolve the error. ...

An issue occurred: Promise was not caught and resulted in an error stating that no routes can be matched for the URL segment 'executions/190'

My current project involves developing the front end by mocking the back-end using the expressjs library. Within my project, I have a file called data.json which stores objects like the following: "singleExecutions":[ {"executionId":190, "label":"exe ...

Preserving type information in TypeScript function return values

Wondering how to make this code work in TypeScript. Function tempA copies the value of x.a to x.temp and returns it, while function tempB does the same with x.b. However, when calling tempA, the compiler seems to forget about the existence of the b field ...

`Switching from Fetch to Axios: A step-by-step guide`

Currently in the process of refactoring some code and need to transition from using fetch to axios. Here's the original code snippet: const createAttachment = async (formData: FormData): Promise<boolean | string> => { try { const respon ...

Customizing page layout for pages wrapped with higher-order components in TypeScript

Looking to add a layout to my page similar to the one in this link: layouts#per-page-layouts The difference is that my page is wrapped with a HOC, so I tried applying getLayout to the higher order component itself like this: PageWithAuth.getLayout Howev ...