Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save alloy/f0c9c90ff7a28f3b17850021488979d5 to your computer and use it in GitHub Desktop.
Save alloy/f0c9c90ff7a28f3b17850021488979d5 to your computer and use it in GitHub Desktop.
import someLiveResolver from "@msteams/some-live-resolver-package";
{
"someLiveResolverBackedField": someLiveResolver as RelayLiveResolverFn<
import("@msteams/some-core-package").LiveResolverContext
// ^......................................................^ - type annotation literal, extracted by relay-compiler from @msteams/frameworks-relay/createRelayEnvironmentTask.ts
>
}
import { getLiveResolverContext } from "@msteams/some-core-package";
export function createRelayEnvironmentTask() {
const store = new LiveResolverStore<
import("@msteams/some-core-package").LiveResolverContext
// ^......................................................^ - type annotation literal, extracted by relay-compiler here
>(
new RelayRecordSource(),
{ liveResolverContextProvider: getLiveResolverContext }
)
// ...
}
export interface LiveResolverContext {
someService: SomeService;
}
export const getLiveResolverContext: () => LiveResolverContext = () => ({
someService: new SomeService(),
})
export const someLiveResolver: RelayLiveResolverFn<
import("@msteams/some-core-package").LiveResolverContext
//^......................................................^ - type annotation literal, written manually by dev
> = (context) => {
// ...
}
@captbaritone
Copy link

Kinda. A few differences:

  1. Generated artifact where resolver is imported would include a type assertion that the context argument matches what's specified in compiler config (in the future this can be enforced by Grats-style static analysis)
  2. Store does not need type parameter

User-defined resolver would look like:

import {LiveResolverContext} from "./some-core-package/index.ts";

/**
  * @RelayResolver Query.someLiveResolver: String
  */
export function someLiveResolver(_args, ctx: LiveResolverContext): string {
  return ctx.someService().greeting();
 }

An example of our existing type assertions for Flow (which could be expanded to support context) can be found here.

With context it would look like:

import type { relayResolver_PopStarNameResolverFragment_name$key } from "relayResolver_PopStarNameResolverFragment_name.graphql";
import userPopStarNameResolverType from "PopStarNameResolver";
import {LiveResolverContext} from "./some-core-package/index.ts";

// Type assertion validating that `userPopStarNameResolverType` resolver is correctly implemented.
// A type error here indicates that the type signature of the resolver module is incorrect.
(userPopStarNameResolverType: (
  rootKey: relayResolver_PopStarNameResolverFragment_name$key,
  ctx: LiveResolverContext // <-- THIS WOULD BE ADDED
) => ?mixed);
// ...

@alloy
Copy link
Author

alloy commented Jun 12, 2024

Store does not need type parameter

So with Flow, would you expect to be able to use Flow itself to infer the type? Because I expect that to lead to performance issues, at least with TS on a codebase like ours.

I'm not against working towards that, but using an explicit type parameter seemed like a good first iteration.

@captbaritone
Copy link

would you expect to be able to use Flow itself to infer the type

No, there's no type inference. The context value in the store is mixed/any/unknown type. There's not technically any type safety between the store and the resolver. However, the type assertions in the generated artifacts ensure your resolvers all use a type that matches what you put in the compiler config (you would specify a module path and export name). From there you just have to manually ensure you use that same single type in your store.

So, it's not fully type safe, but as long as you get it right in that one place (configure your store with the same type you specify in your config) everything else is safe.

@alloy
Copy link
Author

alloy commented Jun 12, 2024

matches what you put in the compiler config (you would specify a module path and export name)

Aha. I was contemplating that, but figured you'd have a much more advanced Flow solution in mind that we'd want to work towards.

My suggestion would make it safe, as we can actually check the input to the store, which is what gets passed to the resolvers. The one thing on my mind, though, and why I was contemplating doing it in the config, was if you'd have different stores with different sets of resolvers in your codebase, in which case we can't just assume that we can extract a single typing and apply it to all resolvers.

Am I missing another reason why we would not want to check the input to the store?

@captbaritone
Copy link

My suggestion would make it safe, as we can actually check the input to the store, which is what gets passed to the resolvers.

I don't follow how that's possible. Maybe you could show how a mismatch between a resolver and the store that's being used to read the resolver would manifest during type checking?

We never actually have a connection between the type of the store and the type of the resolver. They never "see" eachother. The graphql tagged template literals reference fields which may be resolvers, but the type system can't "see" what resolvers are being access there since the connection between the tagged template literal and its generated artifact is only made when the babel transform is applied (which is not visible to typescript).

Even if we could propagate type info from the resolver through the generated artifact to the tagged template literal, those get passed to hooks which are not parameterized. Even if they were, they access the environment (and thus the store) via React context which is inherently untypesafe.

@alloy
Copy link
Author

alloy commented Jun 13, 2024

To be clear, I don't mean safe all the way through relay, but safe in the sense that what is being passed to the store matches what the resolver functions would accept. (If still unclear, I'll rework the gist as a typescript playground sample.)

But as said, a naive approach would break down quickly when there's more than 1 store/context typing in the system.

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