GithubHelp home page GithubHelp logo

apollo-cache-control's Introduction

Archival

This repo was archived by the Apollo Security team on 2023-05-26

Please reach out at [email protected] with questions.

⚠️ Deprecation Notice

This specification is no longer supported by Apollo. Please see our documentation for alternative approaches.


Apollo Cache Control Specification

Apollo Cache Control is a GraphQL extension for fine-grained cache control that can inform server-side or client-side GraphQL caches. It describes a format for a GraphQL API to return information about cache expiration and scope, as well as controls that a client can use to override caching.

This data can inform Apollo Studio or other tools of the cache policies that are in effect for a particular request.

Supported GraphQL Servers

Know of other GraphQL servers which implement this specification? Open a PR to this README to add it to the list!

Response Format

The GraphQL specification allows servers to include additional information as part of the response under an extensions key:

The response map may also contain an entry with key extensions. This entry, if set, must have a map as its value. This entry is reserved for implementors to extend the protocol however they see fit, and hence there are no additional restrictions on its contents.

Apollo Cache Control exposes cache control hints for an individual request under a cacheControl key in extensions:

{
  "data": ...,
  "errors": ...,
  "extensions": {
    "cacheControl": {
      "version": 1,
      "hints: [
        {
          "path": [...],
          "maxAge": <seconds>,
          "scope": <PUBLIC or PRIVATE>
        },
        ...
      ]
    }
  }
}
  • The path is the response path in a format similar to the error result format specified in the GraphQL specification:

This field should be a list of path segments starting at the root of the response and ending with the field associated with the error. Path segments that represent fields should be strings, and path segments that represent list indices should be 0‐indexed integers. If the error happens in an aliased field, the path to the error should use the aliased name, since it represents a path in the response, not in the query.

  • maxAge indicates that anything under this path shouldn't be cached for more than the specified number of seconds, unless the value is overridden on a subpath.

  • If scope is set to PRIVATE, that indicates anything under this path should only be cached per-user, unless the value is overridden on a subpath. PUBLIC is the default and means anything under this path can be stored in a shared cache.

Example

query {
  post(id: 1) {
    title
    votes
    readByCurrentUser
  }
}
"cacheControl": {
  "version": 1,
  "hints": [
    {
      "path": [
        "post"
      ],
      "maxAge": 240
    },
    {
      "path": [
        "post",
        "votes"
      ],
      "maxAge": 30
    },
    {
      "path": [
        "post",
        "readByCurrentUser"
      ],
      "scope": "PRIVATE"
    }
  ]
}

Request Format

Apollo Cache Control also allows clients to include cache control instructions in a request. For now, the only specified field is noCache, which forces the proxy never to return a cached response, but always fetch the query from the origin.

"extensions": {
  "cacheControl": {
    "version": 1,
    "noCache": true
  }
}

apollo-cache-control's People

Contributors

abernix avatar lennyburdette avatar martijnwalraven avatar peakematt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apollo-cache-control's Issues

Unclear how to apply maxAge to PRIVATE scoped paths

Please clarify in your documentation on how maxAge should apply to things that are privately scoped, when not explicitly in the same cache hint.

In your example I guess I can assume that because post, readByCurrentUser, is part of the post path, that it should be 240? But I'm only assuming this because maxAge definition says:

maxAge indicates that anything under this path shouldn't be cached for more than the specified number of seconds, unless the value is overridden on a subpath.

If readByCurrentUser had not overlapped with another path, what would its cacheability be? I'd guess it's the same as no specificed maxAge, but I also don't see any information on the scenario when maxAge is missing?

Provide an example of using sessionAuth when setting up an Engine

I cannot setup sessionAuth for private in-memory cache. Here's how the engine is set up

export default ({ apiKey, WebApp }) => {
  const engine = new ApolloEngine({
    apiKey,
    logging: {
      level: 'ERROR',
    },
    stores: [
      {
        name: 'inMemoryCache',
        inMemory: {
          cacheSize: 104857600, // 100 MB; defaults to 50MB.
        },
      },
    ],
    sessionAuth: {
      header: 'Authorization',
      tokenAuthUrl: 'http://******.ngrok.io/engine-auth-check',
    },
    queryCache: {
      privateFullQueryStore: 'inMemoryCache',
    },
  });

  engine.meteorListen(WebApp);
};

The endpoint /engine-auth-check is defined the following way:

import express from 'express';
import { WebApp } from 'meteor/webapp';

export default () => {
  const app = express();

  app.post('/engine-auth-check', (req, res) => {
    console.log('post request sent');
    res.sendStatus(200);
  });

  WebApp.connectHandlers.use(app);
};

After I run queries a weird behaviour is observed. In traces queries have their scope as "Private" set up as well as TTL, but no cache hits is recorded. Additionally, tokenAuthUrl can be set to any string and no error will be returned.

I've checked the /engine-auth-check endpoint with Postman and it worked fine, but when I run queries, post request seems to not be sent.

Setting s-maxage to header beside max-age

Hello
It would be great if s-maxage would be able to be passed to the cacheHeader. I am using Zeit deployments and to enable the caching the s-maxage would be required.
Thanks

Cache-Control header is not correctly overridden for 0 value

When calculateHttpHeaders is set to true for each request the lowest max age should be caculated for the Cache-Control header. However this is not the case if the @cacheControl directive is set to 0 in the schema.

For example, if the defaultMaxAge is set to 100 and in the schema there is a @cacheControl(maxAge: 0) directive, the Cache-Control header should not be set. However with the current implementation it will be set to max-age=100, public.

The same is the case if defaultMaxAge is set to 0 and the @cacheControl directive of a field should override the one on type level. Here is an example implementation for this:

const { ApolloServer, gql } = require('apollo-server');

const books = [
  {
    title: 'Harry Potter and the Chamber of Secrets',
    author: 'J.K. Rowling',
  }
];

const typeDefs = gql`
  type Book @cacheControl(maxAge: 120) {
    title: String @cacheControl(maxAge: 0)
    author: String
  }
  type Query {
    books: [Book]
  }
`;

const resolvers = {
  Query: {
    books: () => books,
  },
};

const server = new ApolloServer({ 
  typeDefs,
  resolvers,
  cacheControl: {
    calculateHttpHeaders: true,
    defaultMaxAge: 0,
    stripFormattedExtensions: false
  }
});

server.listen().then(({ url }) => console.log(`🚀  Server ready at ${url}`));

Query with: curl -sv 'http://localhost:4000/?query=%7Bbooks%7Btitle%7D%7D'
Expected: no cache-control header
Actual: cache-control: max-age=120, public

The bug seems to be caused by this conditional, which evaluates to false if the checked variable is 0:

error TS2665: Invalid module name in augmentation

I stumbled upon this issue while trying the hello world tutorial for Apollo with TypeScript: node_modules/apollo-cache-control/lib/index.d.ts(20,16): error TS2665: Invalid module name in augmentation. Module 'graphql/type/definition' resolves to an untyped module at 'C:/dev/wks/bedeals-mvp/functions/node_modules/graphql/type/definition.js', which cannot be augmented..

Did you already see this before?

I'm apparently using apollo-cache-control 0.1.0 through apollo-server-express. I also have graphql-tools 2.23.1.

Allow stale response if error

Add a staleIfError directive next to maxAge (seconds) which states that an old response past maxAge might be used if there was an error refreshing the cache. Cached entries older than staleIfError seconds are not allowed to be used.

Similar to https://tools.ietf.org/html/rfc5861#section-4

In this way, Apollo Engine can be used as a fallback cache if the downstream server is unreachable.

Skip calculation of HTTP headers if GraphQL response includes errors

If the calculateHttpHeaders option is set to true, the overall cache policy is always calculated and added as a Cache-Control response header. This might cause issues if there are errors in the GraphQL response, as these erroneous responses would be cached on CDN and client level.

I suggest to not set the Cache-Control header if there are any errors in the GraphQL response. This could either be implemented as the default or configurable by an additional option like noHttpHeadersOnErrors.

working example of cache control?

Hi

I am trying to implement cacheControl following this guide: https://www.apollographql.com/docs/engine/caching.html

I have one single huge REST api which spits out about 1000 fields for each request. and i have added a layer of graphql in my Express server on top of this REST endpoint, with a resolver return a promise.

I have broken down all the fields into about 40 schema files, and on each schema i have added a cachecontrol annotation, like so:

type Agent @cacheControl(maxAge: 240) {
  id: Int!
  name: String
  type: String
}

and I am using apollo-server-express and apollo-engine for the graphql server setup, like so:

import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
import { ApolloEngine } from 'apollo-engine';

const engine = new ApolloEngine({
  apiKey: constants.APOLLO_ENGINE_API_KEY,
  origins: [{
    supportsBatch: true,
    requestTimeout: '60s',
  }],

  // Resize the default in-memory cache.
  stores: [{
    name: 'inMemEmbeddedCache',
    inMemory: {
      cacheSize: 104857600,  // 100 MB; defaults to 50MB.
    },
  }],
  queryCache: {
    publicFullQueryStore: 'inMemEmbeddedCache',
  },
});


app.use(
    '/graphql',
    bodyParser.json(),
    graphqlExpress({
      schema,
      tracing: true, // for apollo engine tracing and reporting to work
      cacheControl: true, // for apollo engine caching mechanism
    }),
  );

versions:
apollo-server-express: 1.3.4
apollo-engine: 1.0.4
express:4.15.2

then when i tested it, i expect my resolver (which will make the HTTP call to that huge REST endpoint) should only gets run ONCE every 240 seconds, but what actually happened is that resolver gets called every time a graphql query gets run regardless, doesn't look like the cache is working, or did i miss anything?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.