2

I'm getting a token with claims from an authentication provider. It's a JSON object that looks like this:

{
  "email": "[email protected]",
  "urn:dev:claims/user-id": "123"
}

I'm trying to create a representative interface or type on my client to access this token keys correctly. The problem is the "dev" part of "urn:dev:claims/user-id" above is dynamic, it comes from environment variable as such:

const claimKey = urn:${process.env.REACT_APP_ENV}:claims/user-id

When I try the following:

interface MyInterface {
  email: string;
  [claimKey]: string;
}

It does not work.

Here's a full reproducible example:

const env = {
  REACT_APP_ENV: "dynamic-value"
}

const token = {
  "email": "[email protected]",
  "urn:dev:claims/user-id": "123"
}

const claimKey = `urn:${env.REACT_APP_ENV}:claims/user-id`

interface MyInterface {
  email: string;
  [claimKey]: string;
}

9
  • That myType definition isn’t valid TypeScript or JavaScript. Could you edit the code here to be a minimal reproducible example? Commented Mar 28, 2021 at 13:40
  • I am missing a question here... ;) ... But I assume that you want to ask how to do this. I can only remark that TypeScript is just a shell over JavaScript that offers some type safety during compilation (to JavaScript). So, as far as I know, you cannot use TypeScript type features (like types and interfaces) for any runtime behavior. I think you need to provide more context to your question and also include your own attempts so far. That way, it will be easier for others to understand what you are trying to accomplish and to help you in more detail. Commented Mar 28, 2021 at 13:40
  • Sorry for that, I hope it's clearer now. Commented Mar 28, 2021 at 13:44
  • Please consider modifying the code in this question to constitute a minimal reproducible example which, when dropped into a standalone IDE like The TypeScript Playground, clearly demonstrates the issue you are facing and does not have other issues (invalid syntax, etc). This should include details of any problem: the phrase "it does not work" is generally not sufficient to convey what the issue is; see the "Describe the problem" section of the minimal reproducible example guidelines. Good luck! Commented Mar 28, 2021 at 14:05
  • 2
    Hmm, I don't know that it's possible to represent a "unique but unknown string" in a way that works with concatenation and doesn't have other side effects. A workaround like this might suffice? If so, I'll write up an answer. (Please also edit the text of the question so that it's a minimal reproducible example without needing to click a link that brings you outside of Stack Overflow.) Commented Mar 28, 2021 at 14:50

1 Answer 1

6

TypeScript does not currently have the capacity to do exactly what you're asking for:

  • You want the compiler to treat process.env.REACT_APP_ENV as some "unknown but unique" string literal, much the way that unique symbol works for symbol-typed values. There was an experimental pull request at microsoft/TypeScript#33038 which would have allowed things like unique string, but it never made it into the language.

  • Moreover, you need to be able to concatenate that unique string to other string literals and have some sort of unique output; maybe this would also require supporting unique string inside "pattern" template literal types as implemented in microsoft/TypeScript#40598, and it isn't obvious that this would work.

  • And even if that were all taken care of, you currently can't use pattern template literal types as object keys; see microsoft/TypeScript#42192. An object type like Record<`foo${string}`, number> is unfortunately treated very much like {}; it will not complain if you assign a type like {fooOops: "This is not a number"} to it. ( Well, at least this part is fixed for TS4.4; pattern template literals can be used in index signatures as per microsoft/TypeScript#44512 )

All of that put together means this is just not within the realm of possibility for TypeScript as of TS 4.4.


Instead you would need some sort of workaround. I was toying around with using a string enum to simulate an opaque/nominal subtype of string that works with keys, but it doesn't really do anything more useful than the workaround I settled on: a placeholder string literal type like "###${process.env.REACT_APP_ENV}###" that we pretend is the known actual type of process.env.REACT_APP_ENV. As long as we only refer to the type as process.env.REACT_APP_ENV and not as the pretend string literal, everything will work out. We might even want the pretend value to be something like "!!!PLACEHOLDER_DO_NOT_USE_THIS!!!" or whatever you need to convince people not to use the literal type.

It would look like this:

declare const process: {
  env: {// the following type is a fiction but is the best I can do 
    REACT_APP_ENV: "###${process.env.REACT_APP_ENV}###"
  }
}

And then your claimKey would be a const-asserted template string so that the compiler can concatenate it and maintain the string-literal-ness of it:

const claimKey = `urn:${process.env.REACT_APP_ENV}:claims/user-id` as const

And everything works as desired, mostly:

interface MyInterface {
  email: string;
  [claimKey]: string;
}

const myInterface: MyInterface = {
  email: "[email protected]",
  [claimKey]: "123"
}

Hooray! Still, it's just a workaround. That placeholder value will likely show up as IntelliSense hints, unfortunately:

const booIntelliSense: MyInterface = {
  email: "",
  "urn:###${process.env.REACT_APP_ENV}###:claims/user-id": "oops" // <-- hinted by IntelliSense!
}

so it's really not perfect. Oh well.

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.