Skip to content
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(types) enable type inference for config property #2802

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

aheber
Copy link

@aheber aheber commented Apr 25, 2022

Adding in the ability to infer config property structures from the WireAdapter definition. This enabled type checking on the allowed properties of the passed WireAdapter. I'm also including a convenience type for applying correct typing to the return value from the wire adapter.

Details

Happy to show a demo or screenshots of the value this provides. Extracting the config structure from the WireAdapter that is provided as the first param and using it to type the second param. Should work with both WireAdapterConstructor and LegacyWireAdapterConstructor, have functional tests on my machine that I can provide.

It is also possible that these changed need to be applied inside of the engine also, not just in the delivered typing.

The major point of this change is to inform the wire decorator of the "shape" of the config param, in this example it informs that recordId is the only allowed property. Passing any different properties, or not passing that property should cause an error condition on type-enforced code.

declare module "@salesforce/apex/TestController.getAccount" {
  export default function getAccount(param: { recordId: string }): Account;
}

  @wire(getAccount, {recordId: "$recordId"}) val;

This leaves a gap, there isn't currently a way to have the decorator apply typing to the decorated property (method or value). If Decorators ever get solidified in Javascript then I expect Typescript will provide a way to accomplish this and can revisit then.

To help with that scenario of typing the returned value I also provide a convenience type that can be used to apply typing to wired properties or methods to extract and deliver the typed return value and apply it automatically to the "data" property of the output. There is a chance that this is an inappropriate place for that change and it should be applied at a higher level, Feedback welcome. (I also cause the error property to be of the type "FetchResponse" but that might be a Salesforce specific concept and not an LWC concept).

Applied as:

  /**
   * @param {WiredValue<[wireAdapter]>} arg0
   */
  @wire([wireAdapter], { recordId: "$recordId" })
  wiredImagePath({ error, data }) {
 
 
  /**
   * @type {[wireAdapter]<getAvatarForId>}
   */
  @wire(getAvatarForId, {recordId: "$recordId"}) val;

Does this pull request introduce a breaking change?

  • 🚨 Yes, it does introduce a breaking change. (maybe...)

Zero change to how the engine/system works. Only in the exposed type interfaces.
Biggest risk I can see is if anyone is using type enforcement and not passing valid config types, this will flag it. Could expose what have likely been errors but are not properly validated.

There are a lot of gaps in my knowledge here, I might have some specific expectations that aren't met by all use cases. Will need some additional review and validation.

Does this pull request introduce an observable change?

  • ✅ No, it does not introduce an observable change.

Adding in the ability to infer config property structures from the WireAdapter definition. This enabled type checking on the allowed properties of the passed WireAdapter.
@nolanlawson
Copy link
Contributor

Thanks for opening the PR!

Happy to show a demo or screenshots of the value this provides.

This would be helpful, thanks. Do you mean it helps with VS code TypeScript checking or something along those lines?

config?: WireConfigValue
export function wire<T, R>(
adapter: WireAdapterConstructor<T> | LegacyWireAdapterConstructor<T, R>,
config?: T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My TypeScript-Fu is weak, but if I'm not mistaken, isn't this broadening rather than narrowing the allowed type here? Should it be something like config?: T extends WireConfigValue instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding another comment of where I think that should be applied. (WireAdapter definition)

interface WireAdapter {
update(config: WireConfigValue, context?: ContextValue): void;
interface WireAdapter<T> {
update(config: T, context?: ContextValue): void;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nolanlawson, I think this one should be config?: T extends WireConfigValue (here on the WireAdapter)(I thought I had that already but this I missed it in my copying from a few different sources). It would only make sense to apply it on the actual wire's config definition if it also applied to the LegacyWireAdapterConstructor as well.

@aheber
Copy link
Author

aheber commented Apr 26, 2022

For the general purpose of these changes, yes giving VS Code/Typescript (typescript checking in Javascript for my specific case) awareness of valid parameters from the adapter shim definition is what I'm going for.

The Salesforce VS Code extensions for LWC automatically generate the following type signatures:

declare module "@salesforce/apex/[controller Apex class].[method name]" {
  export default function [method name](param: {
    [param 0 name]: any;
  }): Promise<any>;
}

The any used in the definition is a bit of a problem that I'll be working on separately. For the purpose of this request I'm trying to make the wire decorator understand that the param object shape from the adapter definition shim should be the same shape as the config parameter passed as the second argument to the wire decorator.

This allows anything implementing a Typescript language server (or applying such to Javascript which is fairly standard) to be able to give property hints when filling out the config obect.

If I tweak one of the adapter signatures like so:

declare module "@salesforce/apex/TestController.testMethod" {
  export default function testMethod(param: {
    foo: string;
    bar: number;
  }): Promise<{ foo: number; bar: string }>;
}

then you can see that with the proposed changes VS Code is now aware of what potential/expected properties are for the config.
image

I plan to try to also work with the extension's team to improve the type generation to improve this integration further but this is the first step down that road. No reason to have better type definitions if the system doesn't know how to apply them.

packages/lwc/types.d.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@nolanlawson nolanlawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aheber Looking back through this PR (and I apologize for the late review), one concern I have is that these types are not consistent with the types defined in @lwc/engine itself, e.g.:

export interface WireAdapterConstructor {
new (callback: DataCallback): WireAdapter;
configSchema?: Record<string, WireAdapterSchemaValue>;
contextSchema?: Record<string, WireAdapterSchemaValue>;
}

Long-term, I think our goal is to make the lwc package merely a wrapper that exports other packages (like @lwc/engine). So rather than have the canonical types defined in lwc, it seems better to have them defined at the source (@lwc/engine).

In the short term, I think it would be acceptable to just update the types in both places, though. I took a crack at it, but ran into some typing issues in @lwc/engine. (I'm not much of a TypeScript wizard. 😅)

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

Successfully merging this pull request may close these issues.

None yet

3 participants