Skip to content

"not in keyof T" - exclude keyof T from string #42315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
yinso opened this issue Jan 13, 2021 · 7 comments
Closed

"not in keyof T" - exclude keyof T from string #42315

yinso opened this issue Jan 13, 2021 · 7 comments
Labels
Duplicate An existing issue was already created

Comments

@yinso
Copy link

yinso commented Jan 13, 2021

Suggestion

๐Ÿ” Search Terms

  • "not in keyof T"
  • Exclude keyof T
  • opposite, outside keyof
  • reverse, inverse keyof

โœ… Viability Checklist

My suggestion meets these guidelines:

  • [ x ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [ x ] This wouldn't change the runtime behavior of existing JavaScript code
  • [ x ] This could be implemented without emitting different JS based on the types of the expressions
  • [ x ] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • [ x ] This feature would agree with the rest of TypeScript's Design Goals.

โญ Suggestion

I am trying to define a type that matches all strings except for keyof T, which seems to be definable as:

type NotInKeyof<T> = Exclude<string, keyof T> // expect to match all string except 'bar' and 'baz'

interface Foo { // keyof Foo = 'bar' | 'baz'
    bar: string
    baz: number
}

const notInKeyofFoo: NotInKeyof<Foo> = 'bar' // expect to error but passes.

However, this doesn't work the way I thought it would. TypeScript currently seems to treat string and the keys of an object as distinct types, so the exclusion doesn't do anything.

This could just be my search skill failing me - if this is already doable in some form, please let me know thanks.

๐Ÿ“ƒ Motivating Example

This reason I am looking for such a type is to write code that allows for incrementally building up an object type, so as I add new keys, the type checker can help catch whether I accidentally used old keys.

I am seeing the need for this pattern in the following scenarios where there is a need to deal with version management and incremental changes - I am sure there are more elsewhere as well:

  • database schema construction
  • form construction
  • API construction

๐Ÿ’ป Use Cases

My current usage is to model database schema changes. The following snippet shows what I am thinking of:

const Accounts = createTable('Account') // type is {} at this point.
  .column('accountId', Number) // this widens the type to { accountId: number }
  .column('name', String) // this further widens the type to { acccountId: number, name: string }

By allowing the type to be incrementally build up, we can model multiple versions of the database in a single code base.

This is already doable today with most of TypeScript's facilities. The gap right now is that when I add a new column, it would be great for TS to help check whether the key was already used, hence the "not in keyof" test:

// time to upgrade to Accounts V2
const AccountsV2 = Accounts
  .column('description', String) // no issues
  .column('accountId', Number) // would be great if TS can flag this.

If this is not yet doable in TS, it seems to me this should be a safe addition to existing code, but certainly would love to know more either way on how this can be considered. Please let me know if there are any questions, thanks.

@MartinJohns
Copy link
Contributor

MartinJohns commented Jan 13, 2021

Duplicate of #38254. You wanted negated types: #29317

@yinso
Copy link
Author

yinso commented Jan 13, 2021

I see - thanks for the quick response ;)

@MartinJohns
Copy link
Contributor

MartinJohns commented Jan 13, 2021

TypeScript currently seems to treat string and the keys of an object as distinct types

To expand a bit on this. keyof T is a union type of several literal types matching the property names of the type T, for example keyof { propA: number; propB: string } results in the union type "propA" | "propB". These literal types "propA" and "propB" are still a string type, just more specific / narrow.

The Exclude<T, E> utility type removes all union members from T that are assignable to E. In your example Exclude<string, ..> it won't do anything, because string is not a union type to begin with. Besides that, string would not be assignable to any literal type, so nothing could be removed.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jan 13, 2021
@yinso
Copy link
Author

yinso commented Jan 13, 2021

Awesome thanks for the additional elaborations. Sounds like the right solution would be based on negated types #29317 when it's available - something like:

type NotKeyof<T> = string & not keyof T

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@justindbaur
Copy link

@yinso You mention being able to do your incremental object creation just without the safety of adding a property multiple times. Do you mind sharing how you were able to achieve that? I'm in a similar boat right now.

@yinso
Copy link
Author

yinso commented Sep 10, 2022

@justindbaur this TS Playground fiddle shows a simple sketch of the incremental idea. let me know if there are any questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants