Skip to content

Instantly share code, notes, and snippets.

@Snugug
Last active June 20, 2018 15:02
Show Gist options
  • Save Snugug/72a6181e6b641d9e32df126135c0ae0c to your computer and use it in GitHub Desktop.
Save Snugug/72a6181e6b641d9e32df126135c0ae0c to your computer and use it in GitHub Desktop.
Need a way to type provider:SOMETHING so I can `new` its children
import { validate, IsInt, IsEmail, IsIn, IsNotEmpty } from 'class-validator';
import { Provider } from './provider';
export class InstanceConfig {
[key:string]: number | string;
@IsEmail()
@IsNotEmpty()
email: string;
@IsInt()
@IsNotEmpty()
duration: number;
}
export class InstanceInstance {
[key:string]: number | string | boolean;
@IsNotEmpty()
template: string;
@IsNotEmpty()
@IsIn(['emea', 'us-central', 'apac', 'aus-sydney'])
region: string;
@IsNotEmpty()
@IsIn(['default', 'on', 'off'])
internetAccess: string;
@IsNotEmpty()
smartrdp: boolean;
@IsNotEmpty()
@IsInt()
idleRuntimeLimit: number;
@IsNotEmpty()
@IsIn(['shutdown', 'off', 'suspend'])
idleRuntimeType: string;
@IsNotEmpty()
@IsInt()
totalRuntimeLimit: number;
}
export class InstanceProvider implements Provider<InstanceConfig, InstanceInstance> {
Config: new() => InstanceConfig;
Instance: new() => InstanceInstance;
}
export interface Provider<ProviderConfig, ProviderInstance> {
Config: new () => ProviderConfig;
Instance: new () => ProviderInstance;
}
import { InstanceProvider, InstanceConfig, InstanceInstance } from './skytap';
import { Provider } from './provider';
import { validate } from 'class-validator';
async function foo<ProviderConfig, ProviderInstance>(provider: Provider<ProviderConfig, ProviderInstance>) {
const config = new provider.Config();
config.email = 'foogmail.com';
config.duration = 10;
let errs = await validate(config, {whitelist: true});
console.log(errs);
}
foo<InstanceConfig, InstanceInstance>(new InstanceProvider());
@chriseppstein
Copy link

chriseppstein commented Jun 18, 2018

I don't particularly understand the difference between the Config and the Instance classes. They appear to be the same. But generally, this appears to be a factory pattern and there's a few ways to accomplish this in TypeScript.

You need an interface for a provider factory. If you can enumerate all possible providers in your codebase at build time, it's generally easier and better. If you need your factory to be extensible by code at runtime from outside of this library, it's a little trickier.

The main thing you probably need to know about is how to define an interface to a constructor method. Read the section on Class Types in the typescript handbook for interfaces. Another example is here

Once you see how that works, I suspect that you can arrive at either having each provider also export a factory class such as:

class ProviderFactory<ConfigType, InstanceType> {
  config(): ConfigType;
  instance(): InstanceType;
}

Or you may also note that given the interface:

interface Provider<ConfigType, InstanceType> {
  Config: new () => ConfigType;
  Instance: new () => InstanceType;
}

that when you import * as provider1 from "./provider1.ts", the provider1 constant implements the interface Provider<provider1.Config, provider1.Instance> and the interface of provider1 is accessible as a type via typeof provider1.

@Snugug
Copy link
Author

Snugug commented Jun 20, 2018

Thanks @chriseppstein, I'm very close, but I'm missing something and I don't quite understand what's missing. With the above (updated) code, I get the following compile errors:

Property 'email' does not exist on type 'ProviderConfig'.
Property 'duration' does not exist on type 'ProviderConfig'.

I seem to be passing everything through properly, and the type validation is now working as I expect, but the actual InstanceConfig class doesn't appear to be used for validation, so it won't let me set the properties as I expect I should be able to.

Thoughts on this last bit? Thanks!

@Snugug
Copy link
Author

Snugug commented Jun 20, 2018

I think I've narrowed it down to https://gist.github.com/Snugug/72a6181e6b641d9e32df126135c0ae0c#file-use-js-L5, specifically that even though I believe I'm passing the types in properly for the generic, it's not actually picking up the structure inside the function. I can confirm that this in general works because if I use InstanceProvider outside of the function call it all works as expected

While that doesn't work, InstanceProvider.Config is not a constructor so I can't new it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment