microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.6k stars 12.44k forks source link

Extend mapped types/default type utils, for better type from class experience #44735

Open RIP21 opened 3 years ago

RIP21 commented 3 years ago

Suggestion

πŸ” Search Terms

get, set, mapped types, remove getters from type, remove setters from type, get public fields only from class

βœ… Viability Checklist

My suggestion meets these guidelines:

⭐ Suggestion

Being able to remove/collect keys of get set types using Mapped Types syntax + many other utils to make types from class definitions based on their access level, type, return type, etc.

πŸ“ƒ Motivating Example

Basically, I have classes everywhere and I want to generate for it a default constructor that is taken from BaseClass that takes all the fields passed and assigns them to the fields of the class.

E.g.

abstract class BaseClass {
  constructor(fields: Record<string, unknown>) {
    for (const field in fields) {
      this[field] = fields[field];
    }
  }
}

That then gets extended.

class User extends BaseClass {
     constructor(
    fields: DefaultConstructorFields<User>
  ) {
    super(fields)
  }
  firstName: string
  secondName: string

  get fullName(): string {
     return this.firstName + " " + this.secondName
  }

  set fullName(full: string) { ... }
}

Something like

type RemoveGetters<Type> = {
  -get [Property in keyof Type]: Type[Property];
};

type RemoveSetters<Type> = {
  -set [Property in keyof Type]: Type[Property];
};

And some "magic" type utils like PublicFields<ClassName> PrivateFields<ClassName or maybe some special syntax to do such things on class definitions to get plain type will be awesome.

Problem is that I cannot make type util e.g. that DefaultConstructorFields, that will ignore all function fields, get fields, replace set with just whatever comes as an argument to the setter.

Currently, it's possible to filter functions/read-only fields/based on type/name, but not get and set, or filter by field visibility such as public protected private and ES6 private #field. Only manual Omit is possible.

πŸ’» Use Cases

Ideally, I want to have my default constructors up and running with minimal overhead. E.g. like Kotlin has data class that just generates POJO with default constructor/hashCode/equals etc. while still allowing for get for calculated properties and set for setting things up while initializing the instance through the constructor.

So my main use case is strict Model classes (typeorm in my case) with lots of annotations and GraphQL Types that are still classes, because it's how Nest.js GraphQL works, it needs a runtime class e.g. prototype to defer the type at runtime.

RIP21 commented 3 years ago

I spent some good time searching for duplicates and Google through the internet and experimented myself guessing as it's supported somehow, but didn't manage to find anything similar to that. If it's a duplicate, I'm sorry :)

RyanCavanaugh commented 3 years ago

-set is readonly, which exists already.

-get is writeonly, which isn't a sort of type we're equipped to reason about.

Not sure what to do with this.

RIP21 commented 3 years ago

@RyanCavanaugh some utility types for class manipulations based on access modifiers maybe? :) There is many things I talked about in the issue :)

I have problem with ignoring getters and setters somehow generically and in bulks and not a one off using Omit.

And, well, readonly field in type extracted from class definition is just field: "value" but for getter it's actually a function. Same for setter. So they're not 1:1 identical as far as I understand.

Also I want to Omit fields based on private / protected and currently its impossible.