Consider this code:
export default class OptimizelyFeatureFlags implements FeatureFlags {
private body: string;
constructor(
protected callEndpoint: CacheableEntity,
protected baseUrl: string,
protected envName: string,
protected featureId: string
) {}
/**
* Calls the Optimizely endpoint to get the info
*/
public async fetch(): Promise<void> {
// Determine if this is a CallEndpoint (or child thereof)
if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
if (!this.callEndpoint.hasAuthorisationToken()) {
throw Error(
'This endpoint requires an auth token to work'
);
}
}
this.body = await this.callEndpoint.getResource(this.baseUrl + `/v2/features/${this.featureId}`);
}
...
}
Now CacheableEntity is an interface, which requires only getResource() to match the contract. The method hasAuthorisationToken is not defined within it.
Now, if a class has a hasAuthorisationToken() method, I know it is a CallEndpoint, which will implement CacheableEntity. This is a type test - from my research I understand that TypeScript does not offer run-time type tests, since at run-time it is merely JavaScript.
However, this won't compile. In a Jest test, I get:
● Test suite failed to run
src/service/feature-flags/OptimizelyFeatureFlags.ts:31:36 - error TS2339: Property 'hasAuthorisationToken' does not exist on type 'CacheableEntity'.
31 if (!this.callEndpoint.hasAuthorisationToken()) {
~~~~~~~~~~~~~~~~~~~~~
I guess there are several ways around this - maybe I can cast the object from CacheableEntity to CallEndpoint? I want to tell the transpiler that I know what I am doing.
Alternatively I could do something like this:
if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
const hasAuth = this.callEndpoint['hasAuthorisationToken']();
...
}
However, I wonder if that is a bit messy. What is the best way around this?