Browse Source

feat: gql codegen + caching + optimistic

Andrew Bastin 3 years ago
parent
commit
52539b084d

+ 1 - 1
.gitignore

@@ -109,4 +109,4 @@ tests/*/videos
 .netlify
 
 # Andrew's crazy Volar shim generator
-shims-volar.d.ts
+shims-volar.d.ts

+ 3 - 0
packages/hoppscotch-app/.eslintrc.js

@@ -18,6 +18,9 @@ module.exports = {
     "plugin:prettier/recommended",
     "plugin:nuxt/recommended",
   ],
+  ignorePatterns: [
+    "helpers/backend/graphql.ts"
+  ],
   plugins: ["vue", "prettier"],
   // add your custom rules here
   rules: {

+ 6 - 0
packages/hoppscotch-app/.gitignore

@@ -110,3 +110,9 @@ tests/*/videos
 
 # Andrew's crazy Volar shim generator
 shims-volar.d.ts
+
+# Hoppscotch Backend Schema Introspection JSON
+helpers/backend/backend-schema.json
+
+# GraphQL Type Generation
+helpers/backend/graphql.ts

+ 1 - 1
packages/hoppscotch-app/components/teams/Team.vue

@@ -73,11 +73,11 @@
 import { useContext } from "@nuxtjs/composition-api"
 import { pipe } from "fp-ts/function"
 import * as TE from "fp-ts/TaskEither"
+import { TeamMemberRole } from "~/helpers/backend/graphql"
 import {
   deleteTeam as backendDeleteTeam,
   leaveTeam,
 } from "~/helpers/backend/mutations/Team"
-import { TeamMemberRole } from "~/helpers/backend/types/TeamMemberRole"
 
 const props = defineProps<{
   team: {

+ 8 - 42
packages/hoppscotch-app/components/teams/index.vue

@@ -71,12 +71,15 @@
 </template>
 
 <script setup lang="ts">
-import { gql } from "@apollo/client/core"
 import { ref, watchEffect } from "@nuxtjs/composition-api"
 import * as E from "fp-ts/Either"
 import { useGQLQuery } from "~/helpers/backend/GQLClient"
+import {
+  MyTeamsDocument,
+  MyTeamsQuery,
+  MyTeamsQueryVariables,
+} from "~/helpers/backend/graphql"
 import { MyTeamsQueryError } from "~/helpers/backend/QueryErrors"
-import { TeamMemberRole } from "~/helpers/backend/types/TeamMemberRole"
 
 defineProps<{
   modal: boolean
@@ -88,47 +91,10 @@ const editingTeam = ref<any>({}) // TODO: Check this out
 const editingTeamID = ref<any>("")
 
 const myTeams = useGQLQuery<
-  {
-    myTeams: Array<{
-      id: string
-      name: string
-      myRole: TeamMemberRole
-      ownersCount: number
-      members: Array<{
-        membershipID: string
-        user: {
-          photoURL: string | null
-          displayName: string
-          email: string
-          uid: string
-        }
-        role: TeamMemberRole
-      }>
-    }>
-  },
+  MyTeamsQuery,
+  MyTeamsQueryVariables,
   MyTeamsQueryError
->(
-  gql`
-    query GetMyTeams {
-      myTeams {
-        id
-        name
-        myRole
-        ownersCount
-        members {
-          membershipID
-          user {
-            photoURL
-            displayName
-            email
-            uid
-          }
-          role
-        }
-      }
-    }
-  `
-)
+>(MyTeamsDocument)
 
 watchEffect(() => {
   console.log(myTeams)

+ 15 - 0
packages/hoppscotch-app/gql-codegen.yml

@@ -0,0 +1,15 @@
+overwrite: true
+schema: https://api.hoppscotch.io/graphql
+generates:
+  helpers/backend/graphql.ts:
+    documents: "**/*.graphql"
+    plugins:
+      - add:
+          content: // Auto-generated file (DO NOT EDIT!!!), refer gql-codegen.yml
+      - typescript
+      - typescript-operations
+      - typed-document-node
+
+  helpers/backend/backend-schema.json:
+    plugins:
+      - urql-introspection

+ 18 - 16
packages/hoppscotch-app/helpers/backend/GQLClient.ts

@@ -6,7 +6,6 @@ import {
   reactive,
   Ref,
 } from "@nuxtjs/composition-api"
-import { DocumentNode } from "graphql/language"
 import {
   createClient,
   TypedDocumentNode,
@@ -28,6 +27,8 @@ import clone from "lodash/clone"
 import { keyDefs } from "./caching/keys"
 import { optimisticDefs } from "./caching/optimistic"
 import { updatesDef } from "./caching/updates"
+import { resolversDef } from "./caching/resolvers"
+import schema from "./backend-schema.json"
 import {
   getAuthIDToken,
   probableUser$,
@@ -49,11 +50,12 @@ const client = createClient({
   exchanges: [
     devtoolsExchange,
     dedupExchange,
-    // TODO: Extract this outttttttt
     offlineExchange({
+      schema: schema as any,
       keys: keyDefs,
       optimistic: optimisticDefs,
       updates: updatesDef,
+      resolvers: resolversDef,
       storage,
     }),
     authExchange({
@@ -142,10 +144,10 @@ export function isLoadedGQLQuery<QueryFailType extends string, QueryReturnType>(
 
 export function useGQLQuery<
   QueryReturnType = any,
-  QueryFailType extends string = "",
-  QueryVariables extends object = {}
+  QueryVariables extends object = {},
+  QueryFailType extends string = ""
 >(
-  query: string | DocumentNode | TypedDocumentNode<any, QueryVariables>,
+  query: TypedDocumentNode<QueryReturnType, QueryVariables>,
   variables?: QueryVariables,
   options: Partial<GQL_QUERY_OPTIONS> = DEFAULT_QUERY_OPTIONS
 ):
@@ -223,19 +225,19 @@ export function useGQLQuery<
 }
 
 export const runMutation = <
-  MutationReturnType = any,
-  MutationFailType extends string = "",
-  MutationVariables extends {} = {}
+  DocType,
+  DocVariables extends object | undefined,
+  DocErrors extends string
 >(
-  mutation: string | DocumentNode | TypedDocumentNode<any, MutationVariables>,
-  variables?: MutationVariables,
+  mutation: TypedDocumentNode<DocType, DocVariables>,
+  variables?: DocVariables,
   additionalConfig?: Partial<OperationContext>
-): TE.TaskEither<GQLError<MutationFailType>, NonNullable<MutationReturnType>> =>
+): TE.TaskEither<GQLError<DocErrors>, DocType> =>
   pipe(
     TE.tryCatch(
       () =>
         client
-          .mutation<MutationReturnType>(mutation, variables, {
+          .mutation(mutation, variables, {
             requestPolicy: "cache-and-network",
             ...additionalConfig,
           })
@@ -244,7 +246,7 @@ export const runMutation = <
     ),
     TE.chainEitherK((result) =>
       pipe(
-        result.data as MutationReturnType,
+        result.data,
         E.fromNullable(
           // Result is null
           pipe(
@@ -253,13 +255,13 @@ export const runMutation = <
             E.match(
               // The left case (network error was null)
               (gqlErr) =>
-                <GQLError<MutationFailType>>{
+                <GQLError<DocErrors>>{
                   type: "gql_error",
-                  error: gqlErr as MutationFailType,
+                  error: gqlErr,
                 },
               // The right case (it was a GraphQL Error)
               (networkErr) =>
-                <GQLError<MutationFailType>>{
+                <GQLError<DocErrors>>{
                   type: "network_error",
                   error: networkErr,
                 }

+ 2 - 2
packages/hoppscotch-app/helpers/backend/caching/keys.ts

@@ -1,7 +1,7 @@
-import { KeyingConfig } from "@urql/exchange-graphcache";
+import { KeyingConfig } from "@urql/exchange-graphcache"
 
 export const keyDefs: KeyingConfig = {
   User: (data) => (data as any).uid,
   TeamMember: (data) => (data as any).membershipID,
   Team: (data) => data.id as any,
-}
+}

+ 14 - 0
packages/hoppscotch-app/helpers/backend/caching/resolvers.ts

@@ -0,0 +1,14 @@
+import { ResolverConfig } from "@urql/exchange-graphcache"
+
+export const resolversDef: ResolverConfig = {
+  Query: {
+    team: (_parent, { teamID }, _cache, _info) => ({
+      __typename: "Team",
+      id: teamID as any,
+    }),
+    user: (_parent, { uid }, _cache, _info) => ({
+      __typename: "User",
+      uid: uid as any,
+    }),
+  },
+}

+ 21 - 91
packages/hoppscotch-app/helpers/backend/caching/updates.ts

@@ -1,18 +1,12 @@
 import { UpdatesConfig } from "@urql/exchange-graphcache"
-import gql from "graphql-tag"
+import { MyTeamsDocument } from "../graphql"
 
 export const updatesDef: Partial<UpdatesConfig> = {
   Mutation: {
     deleteTeam: (_r, { teamID }, cache, _info) => {
       cache.updateQuery(
         {
-          query: gql`
-            query {
-              myTeams {
-                id
-              }
-            }
-          `,
+          query: MyTeamsDocument,
         },
         (data: any) => {
           data.myTeams = (data as any).myTeams.filter(
@@ -31,13 +25,7 @@ export const updatesDef: Partial<UpdatesConfig> = {
     leaveTeam: (_r, { teamID }, cache, _info) => {
       cache.updateQuery(
         {
-          query: gql`
-            query {
-              myTeams {
-                id
-              }
-            }
-          `,
+          query: MyTeamsDocument,
         },
         (data: any) => {
           data.myTeams = (data as any).myTeams.filter(
@@ -56,13 +44,7 @@ export const updatesDef: Partial<UpdatesConfig> = {
     createTeam: (result, _args, cache, _info) => {
       cache.updateQuery(
         {
-          query: gql`
-            {
-              myTeams {
-                id
-              }
-            }
-          `,
+          query: MyTeamsDocument,
         },
         (data: any) => {
           data.myTeams.push(result.createTeam)
@@ -70,77 +52,25 @@ export const updatesDef: Partial<UpdatesConfig> = {
         }
       )
     },
-    renameTeam: (_result, { teamID, newName }, cache, _info) => {
-      cache.updateQuery(
-        {
-          query: gql`
-            query GetTeam($teamID: ID!) {
-              team(teamID: $teamID) {
-                id
-                name
-              }
-            }
-          `,
-          variables: {
-            teamID,
+    removeTeamMember: (_result, { teamID, userUid }, cache) => {
+      const newMembers = (
+        cache.resolve(
+          {
+            __typename: "Team",
+            id: teamID as string,
           },
-        },
-        (data: any) => {
-          data.team.name = newName
-          return data
-        }
-      )
-    },
-    removeTeamMember: (_result, { userUid, teamID }, cache) => {
-      cache.updateQuery(
-        {
-          query: gql`
-            query GetTeam($teamID: ID!) {
-              team(teamID: $teamID) {
-                members {
-                  user {
-                    uid
-                  }
-                }
-              }
-            }
-          `,
-          variables: {
-            teamID,
-          },
-        },
-        (data: any) => {
-          data.team.members = data.team.members.filter(
-            (x: any) => x.user.uid !== userUid
-          )
-          return data
-        }
+          "members"
+        ) as string[]
       )
-    },
-    updateTeamMemberRole: (result, { userUid, teamID }, cache) => {
-      cache.updateQuery(
-        {
-          query: gql`
-            query GetTeam($teamID: ID!) {
-              team(teamID: $teamID) {
-                members {
-                  user {
-                    uid
-                  }
-                }
-              }
-            }
-          `,
-          variables: {
-            teamID,
-          },
-        },
-        (data: any) => {
-          data.team.members = data.team.members.map((x: any) =>
-            x.user.uid !== userUid ? x : result
-          )
-          return data
-        }
+        .map((x) => [x, cache.resolve(x, "user") as string])
+        .map(([key, userKey]) => [key, cache.resolve(userKey, "uid") as string])
+        .filter(([_key, uid]) => uid !== userUid)
+        .map(([key]) => key)
+
+      cache.link(
+        { __typename: "Team", id: teamID as string },
+        "members",
+        newMembers
       )
     },
   },

Some files were not shown because too many files changed in this diff