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

Server side integrations for gqty #2051

Open
kleberbaum opened this issue Jan 22, 2025 · 0 comments
Open

Server side integrations for gqty #2051

kleberbaum opened this issue Jan 22, 2025 · 0 comments

Comments

@kleberbaum
Copy link

kleberbaum commented Jan 22, 2025

Hi everyone,

I’ve been using @snek-at/function (the predecessor project to getcronit/pylon) for quite some time. Over the past three years, snek-function, snek-functions, and snek-query - all previously maintained by @schettn - became core parts of my backend infrastructure. Unfortunately, none of these packages will be actively maintained in the future, so I've been looking for a reliable alternative. That’s why I’m excited about moving to pylon and leveraging GQty, but I’ve hit a few bumps along the way.

1. Dealing with Parameters vs. Arguments

  • Situation
    pylon’s GraphQL setup uses parameters, whereas GQty uses arguments. With snek-query, I can do this:

    async (...params: QueryTypes["getIsUnique"]["params"]): Promise<QueryTypes["getIsUnique"]["return"]> => {
      const context = getContext()
      const header = context.req.header("Authorization")
    
      const args = convertParamsToArgs.Query.getIsUnique(params);
    
      const [isUnique, errors] = await sq.query(q => q.getIsUnique(args), {
        headers: {
          Authorization: header
        }
      })
    
      if (errors) {
        throw new GraphQLError(errors[0].message, {
          extensions: errors[0].extensions,
        });
      }
    
      return isUnique
    }

    Here, convertParamsToArgs and QueryTypes help transform function parameters into the arguments snek-query expects, and QueryParamNames (ordered array of arg keys) keeps everything in the right order. With a bit of reduce() magic the parameters are converted to arguments. Due to TypeScript reasons there is a pregenerated function for each Query and Mutation:

    // schema.generated.ts
    export const convertParamsToArgs = {
    Mutation: {
      userCreate(params: MutationTypes["userCreate"]["params"]): Parameters<Mutation["userCreate"]>[0] {
          return convertParamsToArgsFn<Parameters<Mutation["userCreate"]>[0]>(MutationParamNames["userCreate"], params);
      }
    },
    Query: {
      getIsUnique(params: QueryTypes["getIsUnique"]["params"]): Parameters<Query["getIsUnique"]>[0] {
        return convertParamsToArgsFn<Parameters<Query["getIsUnique"]>[0]>(QueryParamNames["getIsUnique"], params);
      },
      getAllUser(params: QueryTypes["getAllUser"]["params"]): Parameters<Query["getAllUser"]>[0] {
        return convertParamsToArgsFn<Parameters<Query["getAllUser"]>[0]>(QueryParamNames["getAllUser"], params);
      }
    }
    };
  • Problem
    In GQty, variables are alphabetically sorted via lexicographicSortSchema(assertSchema(schema)), which creates type conflicts in TypeScript when optional parameters end up behind required ones. This makes it increasingly difficult to implement an equivalent to QueryParamNames and convertParamsToArgs.


2. fetch({ mode: 'cors' }) and Cloudflare Workers

  • Issue
    On Cloudflare Workers, fetch({ mode: 'cors' }) doesn’t exist and isn't needed. Therefore having a custom pylonClientCode that generates different client code within generator.ts would be cool.

3. Adding pylon as a Supported Framework

  • Suggestion
    It would be awesome if GQty officially recognized pylon the same way it does react and solid-js. That way, anyone building with pylon could have a straightforward entrypoint and a consistent experience.

4. Error Handling with resolve

  • Unclear Docs
    GQty’s docs don’t give me a clear example of how to handle GraphQL errors when calling resolve on the server. If there's a recommended pattern or some best practices, I'd love to see them ❤

My Workaround

I tweaked the GQty generator enough to get a proof-of-concept working with pylon. You can check out a live demo on a Cloudflare Worker
Working Example deployed on Cloudflare Worker:

import { app, getContext } from '@getcronit/pylon'
import { GraphQLError } from 'graphql';

// Gqty
import { resolve } from './clients/iam/index';
import { QueryTypes, MutationTypes, convertParamsToArgs } from './clients/iam/schema.generated';

// Recursively fetch entire object for logging
const getAll = (obj) => {
  for (const key in obj) {
    if (typeof obj[key] === 'object') {
      getAll(obj[key])
    } else {
      console.log(obj[key])
    }
  }
}

export const graphql = {
  Query: {
    getIsUnique: async (...params: QueryTypes["getIsUnique"]["params"]): Promise<QueryTypes["getIsUnique"]["return"]> => {
      const context = getContext()
      const authorizationHeader = context.req.header("Authorization")

      const args = convertParamsToArgs.Query.getIsUnique(params);

      const isUnique = await resolve(
        ({ query }) => {
          const objectGql = query.getIsUnique(args)

          // Just a temproary workaround to get the whole object
          // should be replaced with just the thing that are requested
          // from pylons graphql api.
          getAll(objectGql)

          return objectGql
        },
        {
          extensions: { authToken: authorizationHeader },
          cachePolicy: 'no-store'
        }
      )

      return isUnique
    }
  },
  Mutation: {
    userCreate: async (...params: MutationTypes["userCreate"]["params"]) => {
      const context = getContext()
      const authorizationHeader = context.req.header("Authorization")

      const args = convertParamsToArgs.Mutation.userCreate(params);

      const data = await resolve(
        ({ mutation }) => {
          const response = mutation.userCreate(args)

          // Just a temproary workaround to get the whole object
          // should be replaced with just the thing that are requested
          // from pylons graphql api.
          getAll(response)

          return response
        },
        {
          extensions: { authToken: authorizationHeader },
          cachePolicy: 'no-store'
        }
      )

      return data
    }
  }
}

export default app

While this demonstrates that GQty can work in a pylon environment, it’s definitely not as polished as I’d like.

I will open a pull request with my changes so others can try this out. Since snek-function, snek-functions, and snek-query will no longer be actively maintained in the future, it would be really helpful for anyone who wants to migrate from those old setups into a modern solution with pylon and GQty.

Our friends have been doing server side integrations for gqty, give them a try!

  1. getcronit/pylon
  2. stepci/garph

While we are focusing on client-side DX, we are also open to ideas on server implementations.

With absolutely no commitment, it may happen in gqty if you have great ideas on server API and no one else bothers doing it.

Originally posted by @vicary in #2025 (comment)

@vicary and @schettn – if you have ideas on how to officially support pylon in gqty, especially regarding parameter ordering, dynamic data fetching and error handling please let me know.

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

No branches or pull requests

1 participant