RedwoodGraphQLContext extend

I have a TS error whenever I try use GraphQL context.params.variables that I need on relation resolvers:

relation: (_obj, { root, context }) => {
    return db.thing.findUnique({id:root?.id, date:context.params.variables.date, field:"thing", take: 2})

I found this on node_modules/@redwoodjs/graphql-server/dist/types.d.ts:

/** This is an interface so you can extend it inside your application when needed */
export interface RedwoodGraphQLContext {
    event: APIGatewayProxyEvent;
    requestContext: LambdaContext;
    currentUser?: ThenArg<ReturnType<GetCurrentUser>> | AuthContextPayload | null;
    [index: string]: unknown;
}

I tried extending it in api/types/custom.d.ts (with or without export before interface):

// custom.d.ts

declare module '@redwoodjs/graphql-server' {
    interface RedwoodGraphQLContext {
      params: {variables: any};
    }
}

And my api/tsconfig.json:


{
  "compilerOptions": {
    "noEmit": true,
    "allowJs": true,
    "esModuleInterop": true,
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "skipLibCheck": false,
    "baseUrl": "./",
    "rootDirs": [
      "./src",
      "../.redwood/types/mirror/api/src"
    ],
    "paths": {
      "src/*": [
        "./src/*",
        "../.redwood/types/mirror/api/src/*"
      ],
      "types/*": ["./types/*", "../types/*"],
      "@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/api"],
      // An attempt
      "@redwoodjs/graphql-server": ["./types/custom.d.ts", "../node_modules/@redwoodjs/graphql-server"] 
    },
    "typeRoots": [
      "../node_modules/@types",
      "./node_modules/@types"
    ],
    "types": ["jest"],
  },
  "include": [
    "src",
    "../.redwood/types/includes/all-*",
    "../.redwood/types/includes/api-*",
    "../types", 
    // An attempt
    "./types/custom.d.ts",
    "../web/src/fragments"  
  ]
}

I still have the type error. How can I properly extend the type RedwoodGraphQLContext ?

Hi @Bigood – I think I see what you might be trying to do but could you explain?

My guess is that you have some query – actually real examples help vs general – so let’s say you have and Concert and a Band and a Band has many Events … and events have dates.

You have some query that is:

band {
 id 
 name
 events {
  city
  eventOnDate
 }
}

And you have a search either for a band and some date or some band filter and a date … and you want to make sure the band’s events are only for the date.

Without a filter on the resolver between Band and Event, all events for each band would return.

Is that the scenario you want to solve?

And then passing in the date on the context is how you want to filter?

Personally, I would use the generated simple get/find per model here but write a nee service like

findBandEventsForDate()

and then use a proper Prisma nested read with the include or joins on the event.

Then in the resolver, shortcut it.

I think I could give you a proper example if you give me a less general example and confirm this is what you want to do.

Also, @Bigood you may want to see docs on extending context here: GraphQL | RedwoodJS Docs

Also, @Bigood the arg for the original request are provided here: GraphQL | RedwoodJS Docs

So, if you wanted to access that original date filter value, you should be able to use that in args – I think.

Or – sorry – is your question just about the type and how to extend it?

And you are not creating a new interface and extending it with the context .,… but trying to modify the dist type itself?

interface MyNewContext extends RedwoodGraphQLContext {
 myThing: string
 myNumber: number
}

Thanks for your implication, and sorry if I’ve been unclear: my code works as intended, it’s just Typescript that doesn’t know about any sub-prop, including variables.

Property ‘variables’ does not exist on type ‘unknown’.ts(2339)

I’m absolutely not confident on Typescript, and blindly tried this: Extending TypeScript Global object in node.js - Stack Overflow

The goal was to extend it globally without having to edit anything relying on it, nor any internals, as Redwood seems to import it to produce types on api/types/graphql.d.ts :

import { RedwoodGraphQLContext } from '@redwoodjs/graphql-server/dist/types';
...
export type AlertResolvers<ContextType = RedwoodGraphQLContext, ParentType extends ResolversParentTypes['Alert'] = ResolversParentTypes['Alert']> = {

If you have any idea how to achieve this, I’m on! I have 30 @ts-expect-error waiting to be removed.

Thanks!

So, I’m still a bit confused and am wondering if this isn’t a different problem per se.

But before I get to that:

import type { RedwoodGraphQLContext } from '@redwoodjs/graphql-server'

You don’t need to import from dist – the type is available.

I still don’t know how you are setting params on the context.

It would really help to have a repo to look at or a non-generic example to code along with.

Is the issue that your “relation” isn’t filtering by date even you the root query eager loaded those relations and filtered by date?

I still think you can fetch your parameters from the obj argument and as that should have the query and argument populated.

Also, if you have eager loaded this reaction and already filtered the the date, you can optimize and shortcut

relation: (_obj, { root, context }) => {
   // if in your relation service you already filtered it's things by date and populated via Prisma include
   // then you can shortcut and say, hey I have them, just return them ... and nt query again
    if (root.thing) {
      return root.thing
   }
   // but if no things, then I need to fetch
    return db.thing.findUnique({id:root?.id, date:context.params.variables.date, field:"thing", take: 2})

As I said, a repo would help me figure that out. Thanks,

I’m not importing RedwoodGraphQLContext from dist, I was just showing the content of api/types/graphql.d.ts, which is automatically generated, right?

Sorry I can’t share an example repo right now, I’m on a rush and my code is private.

I still don’t know how you are setting params on the context.
As I said, a repo would help me figure that out. Thanks,

Let me start again with some less generic code then.


If I follow your suggestion, it does work, and Typescript knows about subquery’s args:

export const schema = gql`
  type Thing {
    ...
    relation(date:Date): WaterAnalysis
}

Client side :

const QUERY = gql`
  query Thing($date: Date) {
    thing(date:$date) {      
      id
      firstRelationMatchingDate(date:$date) {
         id
         createdAt
      }
    }
  }

SDL :

export const schema = gql`
 type Thing {
   ...
   firstRelationMatchingDate(date:Date): WaterAnalysis
}

Resolvers :

export const Thing = {
  firstRelationMatchingDate: (_args, gqlArgs) => {
    return db.relation.findFirst(findByDateCondition(gqlArgs.root?.id, _args.date))
  },

With

export const findByDateCondition = (id, date): Prisma.Args<'findFirst'> => ({
    where: {
        idThing: id,
        AND: [
            { createdAt: { gte: dayjs(date, 'YYYY-MM-DD').startOf("day").toDate() } },
            { createdAt: { lte: dayjs(date, 'YYYY-MM-DD').endOf("day").toDate() } }
        ]
    },
    orderBy: { createdAt: 'desc' }
})


Now, what I try to achieve is to use the global date on numerous subqueries, as it is available :

const QUERY = gql`
  query Thing($date: Date) {
    thing(date:$date) {      
      id
      firstRelation1MatchingDate {
         id
         createdAt
      }
      firstRelation2MatchingDate {
         id
         createdAt
      }
      firstRelation3MatchingDate {
         id
         createdAt
      }
    }
  }

SDL :

export const schema = gql`
 type Thing {
   ...
   firstRelation1MatchingDate: Relation1
   firstRelation2MatchingDate: Relation2
   firstRelation3MatchingDate: Relation3
}

Resolvers

export const Thing = {
  firstRelation1MatchingDate: (_args, gqlArgs) => {
    return db.relation1.findFirst(findByDateCondition(gqlArgs.root?.id, gqlArgs.context.params.variables.date))
  },
  firstRelation2MatchingDate: (_args, gqlArgs) => {
    return db.relation2.findFirst(findByDateCondition(gqlArgs.root?.id, gqlArgs.context.params.variables.date))
  },
  ...

date is properly available on context.params.variables, the code works as intended, and a lot of things in my code will be related to this date param (thing of my app as a time travelling state machine), so I thought this would be easier to think of it as is.

I hope this is clearer, if it’s not, I’ll try me best to be more accurate! Thanks!

Thanks @Bigood for the more detail and I think I see what you are looking to do — could you tell me what Thing and Relation really are like Band and Event or Rocket and Launch just so I can make a better example.

For the example, let’s picture a beekeeper app. It would look like a time machine, where you could follow and update the state of your hives, with a date selector that travels you to previous or future periods of your production : the selected date is sent along all requests in the app to filter out Analysis, mainly for graphs.

There would be multiple Apiary-ies, containing multiple Beehive-s, with multiple daily analysis TemperatureAnalysis, BiometryAnalysis and WeightAnalysis.

In that case, Beehive would be Thing, and Relation1TempratureAnalysis and such.

Is that ok?

@dthyresson thanks for all the energy you’ve already put into this.

Were you preparing an example that extended RedwoodGraphQLContext’s interface, or providing a workaround?

Can I do something to help?

I still don’t think the context needs to be adjusted – I believe you can accomplish this with a service that queries the related hives and analyses for the given date:

something like:

getBeehiveWithAnalyses = async ({id, analysisDate}) => {
return await db.beehives.findMany({where: {id: 1, include: {temperatureAnalysis: {analyzedOn: analysisDate} }, biometeryAnalysis: {analyzedOn: analysisDate} }, weightAnalysis: {analyzedOn: analysisDate} }}}})
}

Then define in sdl a query getBeehiveWithAnalyses that returns the expected Trae of a beehive with many analyses.

See: Filtering and Sorting (Concepts) for nested readers and includes.