Reputation: 3687
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;
}
Upvotes: 2
Views: 6070
Reputation: 328362
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 ( Well, at least this part is fixed for TS4.4; pattern template literals can be used in index signatures as per microsoft/TypeScript#44512 )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.
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.
Upvotes: 6