-
-
Notifications
You must be signed in to change notification settings - Fork 190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix thunk and computed alias #791
Fix thunk and computed alias #791
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @arielhs ! Thanks for this contribution 👏
I added a typescript test to cover this issue, but I was unable push it to your repository due to insufficient permissions. Could you add this test yourself? (I just copied your example into a external-type-defs.ts
file in the tests/typescript
folder)
This PR addresses this by adding type markers which forces TypeScript to capture these type variables, fixing the target typing on the model instance.
Awesome!
A concern I have is if someone was using these types in an unconventional way e.g.
Good point. I don't expect this to be heavily utilized, but if so, it is an easy fix for the consumer.
Sadly, when testing this on a large code base, there are some issues. This does not seem to be working well for composed models. I'll try to reproduce this in a small example & post back here. |
I think I was able to reproduce this - it is atleast one of the issues: import { thunk, Thunk } from 'easy-peasy';
type Injections = any;
interface IModelActions<T> {
someThunk: Thunk<this, T, Injections, IStoreModel>;
}
interface IModel<T> extends IModelActions<T> {
someValue: T;
}
interface IStoreModel {
a: IModel<string>;
b: IModel<number>;
}
const createModelActions = <T>(): IModelActions<T> => {
return {
someThunk: thunk(() => {}),
};
};
const storeModel: IStoreModel = {
/*
Type '{ someThunk: Thunk<IModelActions<string>, string, any, IStoreModel, any>; someValue: string; }' is not assignable to type 'IModel<string>'.
Types of property 'someThunk' are incompatible.
Type 'Thunk<IModelActions<string>, string, any, IStoreModel, any>' is not assignable to type 'Thunk<IModel<string>, string, any, IStoreModel, any>'.
Property 'someValue' is missing in type 'IModelActions<string>' but required in type 'IModel<string>'.ts(2322)
example.ts(10, 3): 'someValue' is declared here.
example.ts(15, 3): The expected type comes from property 'a' which is declared here on type 'IStoreModel'
*/
// ❌ Results in the error above
a: {
someValue: 'hello',
...createModelActions<string>(),
},
// ✅ works fine
b: {
someValue: 123,
someThunk: thunk(() => {}),
},
}; If I remove the marker types in the definition, the problem is resolved: export type Thunk<
Model extends object,
Payload = undefined,
Injections = any,
StoreModel extends object = {},
Result = any,
> = {
type: 'thunk';
payload: Payload;
result: Result;
// 👇 When these are "removed", the issue above dissappears 🤔
// [ModelTypeMarker]?: Model;
// [InjectionsTypeMarker]?: Injections;
// [StoreModelTypeMarker]?: StoreModel;
}; I'm not sure how to resolve this issue. We should be able to compose stores, as the example above with the |
@jmyrland I'll look at the example now and see if i can find another way that works. I've also added you as a collaborator to my fork. And thank you for the example, should make chasing this down much easier |
Sweet - I pushed the tests 👍 |
…y-peasy into fix-thunk-and-computed-alias
@jmyrland I found a way to capture the type parameters without creating the assignability issues that would break the composed models like you described in your example. I've just pushed, let me know if this works against the real example - the large codebase you described. I am confident it is a safe technique, see the "Methodish" example here if you are interested |
Awesome! You're a TS wizard! 😁 That fixed the specific issue - but this is whack-a-mole, and another one popped up (generalized output):
I'll see if I can construct a test to reproduce this issue, unless you immediately see the fix. |
Lol I'm happy to keep whack-a-moling, please do make the example |
I've added a new testcase for complex store inheritance, extrapolated from existing code. It is quite convoluted - but if this passes, so should the existing code. |
Elegant solution @arielhs 👏 I've tested the new types against the "Large Codebase ™️" and all is good ✅ |
Thanks again for creating an example. I feel a bit silly, the solution ended up being much simpler... just change |
My pleasure - I very much appreciate the simple solution of using Let's get this released when @ctrlplusb has a moment to spare! 🚀 |
Thanks again for your contribution @arielhs ! 👏 This will be included in the upcoming release this week. |
This is a followup to this PR.
Suppose you had a model where you had factored out the
Thunk
andComputed
types into their own aliases like this:Since the
Thunk
andComputed
types don't actually use theModel
type parameter in their resulting type, it is "lost" when we make a type alias wrappers. This means thecomputed()
andthunk()
generic functions infer their type parameters like this when attempting to use them on the model instance:computed<{}, any, {}, StateResolvers<{}, {}>>
thunk<{}, MyThunkPayload, any, {}, Promise<true>>
Whereas we would expect them to be inferred like this:
computed<MyModel, number, {}, StateResolvers<MyModel, {}>>
thunk<MyModel, MyThunkPayload, any, {}, Promise<true>>
The result on the model instance is:
and
This PR addresses this by adding type markers which forces TypeScript to capture these type variables, fixing the target typing on the model instance.
This has also been used to capture the
StoreModel
andInjections
type parameters.A concern I have is if someone was using these types in an unconventional way e.g.
Before:
After:
So in this sense it's a breaking change.
Fixes: #634