It is difficult to articulate exactly what I want to achieve, so I will post the code. I have a class that should fetch config that should dynamically return the correct config type based on the id specified in the contractor. Question: How should I structure my code solve my problem spesified below?
export type ConfigA = {
id: "configA";
name: string;
propA: string;
};
export type ConfigB = {
id: "configB";
name: string;
propB: string;
};
export type Configs = ConfigA | ConfigB;
export class ConfigService<C extends Configs> {
private readonly configCollection: CollectionReference<C>;
constructor(private configName: C["id"]) {
this.configCollection = db.collection("config") as CollectionReference<C>;
}
public async get() {
const configSnap = await this.configCollection.doc(this.configName).get();
return configSnap.data();
}
}
Class callee
const configA = await new ConfigService("configA").get();
const propA = configA.propA;
The Problem:
Property 'propA' does not exist on type 'Configs'.
Property 'propA' does not exist on type 'ConfigB'
CodePudding user response:
You could use
new ConfigService<ConfigA>("configA").get()
The issue I think is that the specific subtype cannot be enforced to be inferred from that string parameter alone because ConfigA | ConfigB
is already a valid generic type. And the rules for type inference are afaik that it will infer the most generic type that fits.
This way you can "help".
There's another potential workaround that looks like
// ugly, but this is the missing link
type ConfigMap = {
configA: ConfigA
configB: ConfigB
}
export class ConfigService<Id extends Configs['id'], C extends ConfigMap[Id]> {
constructor(private configName: Id) {
...
which seems to give the correct type but you'll have to take care of mapping strings to types yourself. But this time only once, not everywhere. But this can also be achieved by simply doing roughly
export const configServices = {
configA: new ConfigService<ConfigA>('configA'),
configB: new ConfigService<ConfigB>('configB'),
}
...
await configServices.configA.get()
CodePudding user response:
You could use a user-defined type guard (aka type predicate) to distinguish between ConfigA
and ConfigB
in your get()
.
Something like this one:
function isConfigA(config: ConfigA | ConfigB): config is ConfigA {
return (config as ConfigA).propA !== undefined;
}