Issue
I've created a class in TypeScript which uses a generic to allow for method type checking. To simplify:
class Schema<PropType extends Record<string, any>> {
props: Partial<PropType> = {};
constructor(props: PropType) {
this.props = props;
}
pick(...propNames: Array<keyof PropType>) {
return new Schema(propNames.reduce(
(obj, key) => ({ ...obj, [key]: this.props[key] }),
{}
));
}
set(key: string, value: any) {
return new Schema({ ...this.props, [key]: value });
}
}
The purpose of this is to provide type-safety when calling pick, or one of the other methods (not shown here), where prop keys are needed:
const Person = new Schema({ firstName: 'Fyodor', lastName: 'Dostoevsky' });
Person.pick('firstName'); // No issues
Person.pick('madeup'); // Issues
The problem is, when I change the object and declare a new class, it doesn't seem to extend a new PropType type:
const Person = new Schema({ firstName: 'Fyodor', lastName: 'Dostoevsky' });
Person.pick('firstName'); // Works
Person.pick('middleName'); // Shows appropriate type error
const JustFirst = Person.pick('firstName')
JustFirst.pick('firstName'); // Not allowed?!? Error: `Argument of type 'string' is not assignable to parameter of type 'never'.`
const ExtraPerson = Person.set('middleName', 'Mikhailovich');
ExtraPerson.pick('middleName', 'butNothingIsTyped'); // Typing is expanded to any `Record<string, any>`, so no errors
See this TS Playground. Is there a way to have the generic PropType essentially re-initialized/extended with each initialization of the parent class?
EDIT:
I've figured out a solution to set in particular, which I think works:
set<K extends string, V extends unknown>(key: K, value: V) {
return new Schema<PropType & Record<K, V>>({ ...this.props as PropType, [key]: value });
}
Solution
Figured it out with some help from @Etheryte! Example below, but here's the link to the playground.
class Schema<PropType extends Record<string, unknown>> {
props: Partial<PropType> = {};
constructor(props: PropType) {
this.props = props as PropType;
}
pick<K extends keyof PropType>(...propNames: Array<K>) {
return new Schema<Record<K, unknown>>(propNames.reduce(
(obj: any, key: K) => ({ ...obj, [key]: this.props[key] }),
{}
));
}
omit<K extends keyof PropType>(...propNames: Array<K>) {
const toOmit: Partial<PropType> = propNames.reduce((obj, k) => ({ ...obj, [k]: true }), {});
type NewKey = keyof Omit<PropType, K>
return new Schema<Record<NewKey, unknown>>(Object.keys(this.props).reduce(
(obj: any, key: keyof PropType) => toOmit[key] ? obj : ({ ...obj, [key]: this.props[key] }),
{}
));
}
set<K extends string, V extends unknown>(key: K, value: V) {
return new Schema<PropType & Record<K, V>>({ ...this.props as PropType, [key]: value });
}
}
const Person = new Schema({ firstName: 'Fyodor', lastName: 'Dostoevsky' });
Person.pick('firstName');
const JustFirst = Person.pick('firstName')
JustFirst.pick('firstName'); // Allowed
JustFirst.pick('firstName', 'lastName'); // Not allowed
const JustFirst2 = Person.omit('lastName');
JustFirst2.pick("firstName") // Works
JustFirst2.pick("lastName"); // Doesn'!
const All = Person.pick("firstName", "lastName");
const ExtraPerson = All.set('middleName', 'Mikhailovich');
ExtraPerson.pick('middleName', 'notAcceptable', "lastName"); // Only typed keys are allowed, so middleName and lastName are good, but notAcceptable is not
Answered By - Sasha
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.