Skip to content

soundxyz/response-cache

Repository files navigation

@sound-xyz/response-cache

Alternative to @envelop/response-cache with new features:

  • Support for Distributed Redis Locks using RedLock (Opt-in), so your resolvers logic only get's executed once with identical queries
  • Support for setting the response cache dynamically
  • Leverage cached parsed documents for faster TTL calculation based on customs ttlPerSchemaCoordinate.
  • Idempotent redis get calls (Multiple concurrent calls to redis re-use the same promise)

Install

pnpm add @soundxyz/response-cache
yarn add @soundxyz/response-cache
npm install @soundxyz/response-cache

Peer dependencies

redlock is optional

pnpm add ioredis redlock
yarn add ioredis redlock
npm install ioredis redlock

Usage

Configuration

Most of the configuration is the same as with @envelop/response-cache

import Redis from "ioredis";
import RedLock from "redlock";

export const redis = new Redis();
export const redLock = new RedLock([redis], {});
import {
  createRedisCache,
  useResponseCache,
  UseResponseCacheParameter,
} from "@soundxyz/response-cache";
import ms from "ms";

import { redis, redLock } from "./redis";

export const responseCache = createRedisCache({
  // ioredis instance
  redis,
  // Don't specify or set to `null` to disable
  redlock: {
    // Client created calling the `redlock` package
    client: redLock,
    // The default is 5000ms
    duration: 5000,
    settings: {
      // The default is ((duration / retryDelay) * 2)
      retryCount: (5000 / 250) * 2,
      // The default is 250ms
      retryDelay: 250,
    },
  },
});

const cacheConfig: UseResponseCacheParameter = {
  cache: responseCache,
  // cache operations for 1 hour by default
  ttl: ms("1 hour"),
  ttlPerSchemaCoordinate: {
    "Query.fooBar": 0,
  },
  includeExtensionMetadata: true,
};

// ...

({
  plugins: [
    //...
    useResponseCache(cacheConfig),
  ],
});

Dynamic TTL

We have to add the ResponseCache context type in your custom context:

import type { ResponseCacheContext } from "@soundxyz/response-cache";

export interface Context extends ResponseCacheContext {
  // ...
}

Then you can use it directly on your resolvers:

// ...
makeExecutableSchema({
  typeDefs: `
  type Query {
    hello: String!
  }
  `,
  resolvers: {
    Query: {
      hello(_root, _args, context: Context) {
        // Get the current expiry to be used for the cache TTL
        const expiry = ctx.$responseCache?.getExpiry();

        // You can use any logic
        if (expiry != null && expiry > 500) {
          // Set the expiry to any arbitrary value in milliseconds
          context.$responseCache?.setExpiry({
            // TTL in ms
            ttl: 1000,
          });
        }

        return "Hello World";
      },
    },
  },
});