houdini icon indicating copy to clipboard operation
houdini copied to clipboard

get the clean data types without the fragment types

Open macmillen opened this issue 2 years ago • 7 comments

Describe the feature

I want to create mocked data to test my svelte components with houdini fragments inside with a mocked service worker. For that I need the pure graphql types of the query without the $fragments keys.

I can achieve that with some really ugly type acrobatics:

type DashboardPayload = CleanseHoundini<
  Omit<Dashboard$result["dashboard"], "tiles"> & {
    tiles: (Omit<Dashboard$result["dashboard"]["tiles"][number], "widget"> & {
      widget: Widget$data;
    })[];
  },
  "Dashboard"
>;

export const baseDashboard: DashboardPayload = {
  __typename: "Dashboard",
  tiles: [{ widget: { id: "expected_visitor_count", ... } }],
};

the qgraphql query in +page.gql:

query Dashboard {
  dashboard {
    tiles {
      id
      position
      size
      widget {
        ...Widget
      }
    }
  }
}

the fragment inside the svelte component:

  $: widget = fragment(
    _widget,
    graphql(`
      fragment Widget on DashboardWidget {
        id
        timeSpan
        dataType
        data {
          ... on DashboardDataIntValue {
            prevValue
            value
          }
          ... on DashboardDataMoneyValue {
            prevMoneyValue: prevValue
            moneyValue: value
          }
          ... on Error {
            key
            messages
          }
        }
      }
    `)
  );

But that's really a bad dev experience.

Proposed solution

  • either output the types like XYZ$pure
  • or create a type helper to cleanse the types like Purify<XYZ>

Criticality

cool improvement, my projects will benefit from it

macmillen avatar Jun 21 '23 11:06 macmillen

👋 Hey @macmillen!

There are already $data types exported for each fragment that contain just the shape of the fragment - does that work?

AlecAivazis avatar Jun 22 '23 07:06 AlecAivazis

hey @AlecAivazis i'm already using these as you can see in the example code (Widget$data). the thing is that i want to have the pure parent type with the fragments being the $data types without me having to do these crazy type acrobatics

macmillen avatar Jun 22 '23 08:06 macmillen

Ah! I understand now. Sorry for not reading your message more carefully.

I think I am in favor of your second option to create a type helper. Would you be willing to submit a PR that adds a FlattenSelection utility type that does exactly that?

AlecAivazis avatar Jun 28 '23 01:06 AlecAivazis

let's say we want to transform this type:

export type MyDashboard$result = {
    readonly dashboard: {
        readonly tiles: ({
            readonly id: string;
            readonly widget: {
                readonly " $fragments": {
                    Widget: {};
                };
            };
        })[];
    };
};

into this:

export type MyDashboard$result = {
    readonly dashboard: {
        readonly tiles: ({
            readonly id: string;
            readonly widget: Widget$data;
        })[];
    };
};

actually i don't think it's possible to create a type helper because in this example it would need to be aware of the Widget$data to replace the widget fragment type with Widget$data. i'm not sure but i think the only solution is to generate the types

macmillen avatar Jun 28 '23 08:06 macmillen

I think we could pull it off by generating a type that maps those strings to the $data types and then index it in the helper using the string in the $fragments key. That being said, i'm not sure its worth the effort: using MyQuery$whatever is pretty much the same as Whatever<MyQuery> and the codebase already has all of the logic to generate the definition.

I'm good with whichever option you prefer

AlecAivazis avatar Jun 28 '23 08:06 AlecAivazis

I think the type generation would be easier than writing the type helper but i don't think i am able to write a PR for that unfortunately

macmillen avatar Jun 28 '23 11:06 macmillen

EDIT :

Turns out simply adding defaultFragmentMasking: 'disable', to my houdini.config.js solved my issue here.

It might not be the optimal way to use GraphQL, but I'm happy that this option exist, I will be able to use fragments how I intended to until the point I decide to change my approach completely haha

Thanks to @jycouet for the pointer!


A thousand times this - currently, fragments are literally not usable in my codebase because the types are messed up.

Types generated Values provided

I'm okay with extending the type with a " $fragments" key, but at least add the fragment data to the returned type, as currently, in my example, sessionUser has no keys in typescript, yet all the data is there in the actual javascript object.


I do have to say that I don't use the stores as "usual" per se, as I mainly use the client as server only loads. Therefore, my problem is similar in that I would need to use convoluted type acrobatics just to get the data structure, which is what I would have hoped not to do using this library.

V-ed avatar Nov 21 '23 19:11 V-ed