0

I am trying to pass an object as a key to an instance of Dataloader. I know that I have to make a custom cache key function but I don't know exactly how to do it. I'm following this tutorial online: https://www.youtube.com/watch?v=I6ypD7qv3Z8&t=41601s&ab_channel=BenAwad

It's a bit out of date so I can't follow it to a tee.

Im calling the dataload function like so:

@FieldResolver(()=> Int, {nullable:true})
    async voteStatus(
        @Root() post: Post,
        @Ctx() {upvoteLoader, req}: MyContext){
           if (!req.session.userId) {
            return null
           }
        const key = {
            postId: post.id,
            userId: req.session.userId
        } 
        const upvote  = await upvoteLoader.load(key)

        console.log("upvote data: ", upvote)
        return null
        // const upvote  = await upvoteLoader.loadMany(key)
        // console.log("after")
        // return upvote ? upvote.value : null
    }

Looking at the code for DataLoader I get this:

declare class DataLoader<K, V, C = K> {
  constructor(
    batchLoadFn: DataLoader.BatchLoadFn<K, V>,
    options?: DataLoader.Options<K, V, C>,
  ); ... }
declare namespace DataLoader {
  // If a custom cache is provided, it must be of this type (a subset of ES6 Map).
  export type CacheMap<K, V> = {
    get(key: K): V | void;
    set(key: K, value: V): any;
    delete(key: K): any;
    clear(): any;
  };
   ...
  /**
     * Default `key => key`. Produces cache key for a given load key. Useful
     * when keys are objects and two objects should be considered equivalent.
     */
    cacheKeyFn?: (key: K) => C;

I made a custom function for the cache keys. I have tried a couple different ways. (I will come up with better names later)

way 1:

class C {
  postId: number;
  userId: number;
  constructor(postId:number, userId: number) {
    this.postId = postId
    this.userId = userId
  }
}


function cacheKeyFn({postId, userId}: {postId:number, userId: number }) {
  const c = new C(postId, userId) 
  return c;
}

export const createUpvoteLoader = () => 
  new DataLoader<{postId: number; userId: number}, Upvote | null, C>  (async (keys)=>{
    console.log("my keys are ", keys)
    const upvotes = await Upvote.findBy({ 
        postId: In((keys).postId as any[]),
        userId: In(keys as any[])
    })
    const UpvoteIdsToUpvote: Record<string, Upvote> = {}

    upvotes.forEach(upvote => {
        UpvoteIdsToUpvote[`${upvote.userId}|${upvote.postId}`] = upvote
    })

    return keys.map(key => UpvoteIdsToUpvote[`${key.userId}|${key.postId}`])
}, {cacheKeyFn})

Way 2:

function cacheKeyFn({postId, userId}: {postId:number, userId: number }) {
  
  return {"postId": postId, "userId":userId};
}

export const createUpvoteLoader = () => 
new DataLoader<{postId: number; userId: number}, Upvote | null>  (async (keys)=>{
    const upvotes = await Upvote.findBy({ 
        postId: In(keys as any[]),
        userId: In(keys as any[])
    })
    const UpvoteIdsToUpvote: Record<string, Upvote> = {}

    upvotes.forEach(upvote => {
        UpvoteIdsToUpvote[`${upvote.userId}|${upvote.postId}`] = upvote
    })

    return keys.map(key => UpvoteIdsToUpvote[`${key.userId}|${key.postId}`])
}, {cacheKeyFn})

way 3 (for sanity):

export const createUpvoteLoader = () => 
new DataLoader<{postId: number; userId: number}, Upvote | null>  (async (keys)=>{
    const upvotes = await Upvote.findBy({ 
        postId: In(keys as any[]),
        userId: In(keys as any[])
    })
    const UpvoteIdsToUpvote: Record<string, Upvote> = {}

    upvotes.forEach(upvote => {
        UpvoteIdsToUpvote[`${upvote.userId}|${upvote.postId}`] = upvote
    })

    return keys.map(key => UpvoteIdsToUpvote[`${key.userId}|${key.postId}`])
})

The error that keeps coming up is: driverError: error: invalid input syntax for type integer: "{"postId":317,"userId":2}"

jpr
  • 63
  • 5

1 Answers1

0

The output of cacheKeyFn should be a simple value like string or number. I believe this should work:

new DataLoader<{postId: number; userId: number}, Upvote | null, string> (
    async keys => { ... }, // batchLoadFn
    {
        cacheKeyFn: ({postId: number, userId: number}) => `${postId}:${userId}`
    }
);

To test for uniqueness of a key, DataLoader will invoke cacheKeyFn and use the result for the comparison. Here, we create a string ${postId}:${userId}. The batch load fn will receive the original object, not the string. Also see: https://stackoverflow.com/a/59349421

Strix
  • 1,094
  • 1
  • 10
  • 18