those of you using graphql - how are you organizin...
# random
t
those of you using graphql - how are you organizing your operations in your frontend?
a
following this question. We use Vuex with Vue. Sub divide the Vuex into different modules based on logical similarities. Any GQL call is run in the store via action, then we commit relevant results to the store and use getters to fetch the data in the component, unless the GQL call is scoped totally to the component, in which case we just store the data on the component Really open ended on the best way to initialize user state when they log in. currently do all data in one api call. Team is considering pro/con of doing state load in several calls in parallel instead of single call. would love advice on that
t
Are your operations defined in their own .graphql files or inline with your code? also are you using typescript?
a
inline in code. not typescript currently. like everyone who writes JS we plan to switch to TS soon ™️
we use aws appsync for managed gql
t
Yeah inline with code is convenient but with the current tooling it's not easily used with typesafety. I'm trying to achieve this experience:
er the gif is here:

https://github.com/babyfish-ct/graphql-ts-client/raw/master/graphql-ts-client.gif

100% typesafe, query is inline with code, and codegen on on serverside changes
m
I use https://www.graphql-code-generator.com/ & graphql-request to generate a strongly typed client
t
Yeah that works but I wanted a workflow that'll work with any graphql client (I prefer urql) And I just found a pretty good one! https://www.graphql-code-generator.com/plugins/gql-tag-operations-preset This is pretty incredible - can define the gql tags inline and it'll inject typings behind the scenes that match the exact string
r
I have been using
graphql-codegen
to generate types for my packages into a package e.g.
Copy code
overwrite: true
schema:
  - <redacted>
  - <redacted>/appsync.graphql

config:
  scalars:
    AWSJSON: string
    AWSDate: string
    AWSTime: string
    AWSDateTime: string
    AWSTimestamp: number
    AWSEmail: string
    AWSURL: string
    AWSPhone: string
    AWSIPAddress: string

generates:
  <redacted>
    plugins:
      - typescript
I have my own schemas, and also a simple schema for Appsync types, e.g.
Copy code
scalar AWSDate
scalar AWSTime
scalar AWSDateTime
scalar AWSTimestamp
scalar AWSEmail
scalar AWSJSON
scalar AWSURL
scalar AWSPhone
scalar AWSIPAddress
then share the types across the packages via the
types-package
Although I for very simple front end queries I'll go with the
const gql = _String_.raw;
. For the backend I always use the types though~.. Some of the UI is generated during runtime so I am using some JS in the stack for quick wins 🙂
o
I use
typescript-operations
and `typescript-urql`:
Copy code
schema: ../services/graphql/exports/introspection-result.json
documents: './src/**/*.query.ts'
generates:
  src/hooks/internalApi.ts:
    config:
      withHooks: true
      defaultScalarType: string
    plugins:
      - add:
          content: "/* This file was generated by `yarn gen-graphql`. Don't edit it directly */ \n"
      - typescript
      - typescript-operations
      - typescript-urql
    hooks:
      afterOneFileWrite:
        - prettier --write
        - eslint --fix
Seems to do what
gql-tag-operations-preset
does, although I need to look more into doing fragments with this setup vs the preset. Hadn’t heard of it before
t
I was using typescript operations before with the near file preset so I could place operations adjacent to the components that use them
g
have you tried graphql-zeus?
there should be an easy way to combine (just) the query builder functionality with any client; I've seen examples for apollo, no reason to think its not possible with urql
t
ooo this looks promising
g
g
Hah! I've worked with Ivan 😄 we started chatting about typed GQL before zeus was a thing 😄
yeah that one seems like a no-brainer QOL improvement right there
t
ah cool. Yeah I really want to write operations in TS, I'm not sure I love zues API but I'm not too picky at this point
g
i think i want to try and turn that issue into a PR
t
you'll be my hero
g
Tried it. Small issue there 🙂
the way zeus treats input types doesn't quite lead to this kind of use case
afaict there is no "input params collection" option
instead, you pass the params at query build time
but maybe it can be hacked in
hmm nope, to do that, a template literal array must be produced by zeus
instead, zeus does its own variable substitution already
t
Ah sounds like Ivan ran into the same thing
I also saw a different issue referencing this I think
g
hm. actually i think it might work
need to test with a larger example
😄
t
🎢
d
I used https://nexusjs.org in the past amd really liked it. I think it has an option to output .graphql files and ts types
g
Final conclusion from y-day was a nope @thdxr
afaict, Zeus simply produces parameterless queries
t
That's sad
I wish I had some time I'd love to build a version of this that's 1. A GraphQL codegen plugin since people are probably already using that 2. Emits typed-document-node
g
@thdxr curious, any reason you prefer parameterized queries to the ability to just build an ad-hoc one?
I think the idea of writing a typed builder in TS is the flexibility to write any query ad-hoc 🙂
t
I was just thinking about that, I don't think I care that much
slight performance benefit to parameterizing them, but impractical to really consider
I think I can turn make a plugin wrapping the codegen for zeus with graphql-codegen and can shove the typed document node version in there
can you share your sample code?
g
i stubbed out the plugin but before deciding what to generate i started playing around with what outcome I'd like here https://github.com/spion/graphql-zeus/blob/feat/typed-document-node/examples/typescript-node-big-schema/src/zeus/typedDocumentNode.ts
i was trying to generate a params type
type ParamsType<SRC> = SRC extends [infer Params, infer Result] ? Params : { [K in keyof SRC]: ParamsType<SRC[K]> };
but in actual reality... that is not the query that zeus generates
zeus generates a parameterless
query{ stuff here }
t
nice you thinking about submitting a PR?
g
Yeah, just wanted to try it out a little bit
t
yoo checkout this insanity: https://github.com/gqty-dev/gqty
g
yeah tql is neat but it doesn't work if params are not toplevel afaict
and gqty is so magical that I fear it 😅
the caveats are a bit weird https://gqty.dev/docs/intro/how-it-works
seems like conditional rendering would be broken (except maybe for unions explicitly declared in gql, idk)
t
Yeah it is a bit magical and I don't really need the autotracking I just like the api + the fact it has cache normalization
also found this: https://github.com/remorses/genql and it also has an issue for TypedDocumentNode lol - actually opened by @Julien Goux
g
I think I need to build a proper generic schema-to-TS mapper 😄
looking at what zeus does, there is a runtime representation of the data structures but its untyped
there should be a typed representation of the schema, as well as a
typeof GetType<X>
where X is any subset of the schema
t
doesn't some of graphql-codegen do that
I'm not super familiar with its output but I have poked around it
g
there are many options so maybe... my problem is that most options generate the types and runtime representations separately
i can tell you what I wanted to do... I wanted to build
createQuery((vars, q) => q({user: [{id: vars.id}, {name: true}]}))
t
yep me too
and I want to be able to use that with any gql client
g
but for that you need both the runtime value that denotes the name of the type of
vars.id
as well as its type
so you want both a runtime representation of the schema (with all the type names available at run time) that also has a typeof helper to give you any type of any bit of it
t
oh I see what you're saying - yeah all the codegen stuff now is focused on types only, no runtime structure
g
some of it does both... but they're not related
and it has to be via typeof because you can't map back from types to runtime values due to erasure 😄
t
right
j
I think the Guild is working on a typed client as well : https://github.com/dotansimha/graphql-code-generator/pull/7383
t
there's so many projects in this category it feels weird to me that no one is taking the exact approach I'm looking for
ohhh interesting - that's exactly what I'm looking for!
j
Another nice project I have under my radar is https://github.com/timkendall/tql
t
Yeah I mentioned tql above but the api feels kinda crazy
Copy code
import { sdk } from './generated'

const UserQuery = sdk.query({
  name: "UserById",
  variables: {
    userId: "ID!"
  },
  selection: {
    user: {
      [sdk.arguments]: {
        byId: "userId"
      },
      id: true,
      login: true,
    },
  },
});
this is an interesting api with the
[sdk.arguments]
symbol
I'll test drive tql and see
j
If the function calls could be removed tql would be very good imo : https://github.com/timkendall/tql/issues/98
t
ah yeah
j
It's very similar to Nexus / pothos
t
are you using nexus or pothos?
j
I'm using Nexus yes but if I had to start a new project today I'll go for pothos now
No continuous codegen step and more maintained / feature rich in term of plugin
t
I need to explore pothos, I considered nexus for our graphql-stack but didn't love the experience
j
The new version of graphql yoga based on envelop with pothos for the schema building would be my pick 😁
t
we're using helix currently which is what gql yoga uses under the hood
wow evelop seems so cool, I missed it
should be able to integrate
g
pothos is code-first huh
d
Do you still use Apollo for Lambda server or did you move to something else?
t
we built something using graphql-helix
which is a low level primitive for gql servers
And then I use pothos on top of that
d
I saw your tweet on Pothos 🙂 So using that, and presumably can switch to your thing once that’s released ? 😉
t
yep we're aiming for a release at the end of April
lmk if you have any pothos questions
d
Is there an example on how to split schema into multiple files?
t
it's nothing super special, you can define things across multiple files and as long as you import all those files everything will get picked up
Copy code
import { builder } from "./builder"

import "./types/auth"
import "./types/business"
import "./types/workspace"
import "./types/user"
import "./types/session"
import "./types/customer"

export const schema = builder.toSchema({})
d
This works
t
I don't even know what QueryFieldBuilder is!
d
ok so how do you export types and add them to schema ?
t
I just define things like this
Copy code
builder.queryFields((t) => ({
  rootSession: t.field({
    type: RootSessionType,
    resolve: async () => {
      const actor = context().assert.root()
      const session = await Auth.getCredentials(actor.properties.id)
      return session!
    },
  }),
  credentials: t.field({
    type: CredentialsType,
    resolve: async () => {
      const user = context().assert.user()
      return user
    },
  }),
}))
your way works too and maybe is more "correct" because mine depends on importing a file and picking up all the side-effects
d
Copy code
import { builder } from './Builder'
import { HelloQuery } from './Query/Hello'

builder.queryType({
  fields: (t) => ({
    hello: HelloQuery,
  }),
})
This is my server
What I’d like is to export something nice from in HelloQuery file, but not sure if it can be done better to what I got to
t
yeah if you want to be explicit I think your way is correct
d
“you can define things across multiple files and as long as you import all those files everything will get picked up” this is not working for me, is there a TS config option that I’m missing ?
I don’t get the state in the imported module
t
d
I’m trying to do that 🙂 how does
./types/auth
have access to builder ?
t
it imports from
./builder
This is
builder.ts
Copy code
import SchemaBuilder from "@pothos/core"
import { Context } from "@bumi/core/context"
import { JSONResolver, DateResolver, LocalDateResolver } from "graphql-scalars"
import RelayPlugin from "@pothos/plugin-relay"
import { decodeTime } from "ulid"
import { VisibleError } from "@bumi/core/errors"

export const builder = new SchemaBuilder<{
  Context: Context
  Scalars: {
    ID: {
      Input: string
      Output: string
    }
    Date: {
      Input: Date
      Output: string
    }
    LocalDate: {
      Input: string
      Output: string
    }
    JSON: {
      Input: string
      Output: string
    }
  }
}>({
  plugins: [RelayPlugin],
  relayOptions: {},
})
builder.scalarType("ID", {
  serialize: (x) => x,
  parseValue: (x) => {
    if (typeof x !== "string" || !decodeTime(x))
      throw new VisibleError(`Invalid ULID: ${x}`)
    return x
  },
})
builder.addScalarType("JSON", JSONResolver, {})
builder.addScalarType("Date", DateResolver, {})
builder.addScalarType("LocalDate", LocalDateResolver, {})

builder.queryType({})
builder.mutationType({})
and an example type
Copy code
import { builder } from "../builder"
import { Workspace } from "@bumi/core/workspace"
import { WorkspaceType } from "./workspace"

import { User } from "@bumi/core/user"

export const UserType = builder
  .objectRef<User.UserEntityType>("User")
  .implement({
    fields: (t) => ({
      id: t.exposeID("userID"),
      email: t.exposeString("email"),
      workspace: t.field({
        type: WorkspaceType,
        resolve: (user) => Workspace.fromID(user.workspaceID),
      }),
    }),
  })
d
ok I think I’m only missing this:
Copy code
builder.queryType({})
builder.mutationType({})
t
ah
d
Yip 🤦‍♂️
That does it. Thanks.
g
Just noticed that Zeus exports
$
which you can use to form variables i.e. `$`id`` 🤕
i think i might pick this up again 😅
t
I went on a crazy path lol, I ended up getting genql working with URQL, helped me understand the structure better
and now I'm coming back to thinking about getting it working with zeus since I think zeus is more active and has support for things like aliases
g
dang, I think I did it
Copy code
const q = tq({
  cardById: [
    {
      cardId: $$('test'),
    },
    {
      Attack: true,
      Defense: true,
    },
  ],
});
this is the API
this is the resulting type:
Copy code
const q: TypedDocumentNode<{
    cardById?: {
        Attack: number;
        Defense: number;
    } | undefined;
}, {
    test: string | null | undefined;
}>
who needs crossword puzzles when you have typescript's Turing complete type system :D
now to make type errors less obscure when you accidentally fat-finger part of the query
t
wow nice!
g
ugh, Zeus changed its variable system entirely now 😞
support for auto-inferred deep variables (including in arrays like Hasura
and
and
or
conditions), standard selection in tql style (but without mandatory method calls for fields), aliases