Skip to content

Commit

Permalink
[WIP] Add support for cacheHints
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie committed Mar 23, 2021
1 parent a8be4c8 commit 6321f49
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 93 deletions.
7 changes: 5 additions & 2 deletions packages-next/keystone/src/lib/createKeystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ export function createKeystone(
Object.entries(lists).forEach(([key, { fields, graphql, access, hooks, description, db }]) => {
keystone.createList(key, {
fields: Object.fromEntries(
Object.entries(fields).map(([key, { type, config }]: any) => [key, { type, ...config }])
Object.entries(fields).map(([key, { type, config }]: any) => [
key,
{ type, cacheHint: config.graphql?.cacheHint, ...config },
])
),
access,
queryLimits: graphql?.queryLimits,
Expand All @@ -76,6 +79,7 @@ export function createKeystone(
itemQueryName: graphql?.itemQueryName,
hooks,
adapterConfig: db,
cacheHint: graphql?.cacheHint,
// FIXME: Unsupported options: Need to work which of these we want to support with backwards
// compatibility options.
// adminDoc
Expand All @@ -86,7 +90,6 @@ export function createKeystone(
// singular
// plural
// path
// cacheHint
// plugins
});
});
Expand Down
2 changes: 2 additions & 0 deletions packages-next/types/src/base.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CacheHint } from 'apollo-cache-control';
import type { KeystoneContext } from './context';
import type { BaseGeneratedListTypes, GqlNames } from './utils';

Expand All @@ -16,6 +17,7 @@ export type BaseKeystone = {
itemQueryName?: string;
hooks?: Record<string, any>;
adapterConfig?: { searchField?: string };
cacheHint?: ((args: any) => CacheHint) | CacheHint;
}
) => BaseKeystoneList;
connect: (args?: any) => Promise<void>;
Expand Down
10 changes: 7 additions & 3 deletions packages-next/types/src/config/lists.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CacheHint } from 'apollo-cache-control';
import { AdminMetaRootVal } from '../admin-meta';
import type { BaseGeneratedListTypes, MaybePromise, JSONValue } from '../utils';
import type { ListHooks } from './hooks';
Expand Down Expand Up @@ -194,6 +195,11 @@ export type FieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> = {
fieldMode?: MaybeItemFunction<'edit' | 'read' | 'hidden'>;
};
};
graphql?: {
cacheHint?:
| ((args: { results: any[]; operationName: string; meta: boolean }) => CacheHint)
| CacheHint;
};
};

export type MaybeSessionFunction<T extends string | boolean> =
Expand All @@ -209,7 +215,7 @@ export type MaybeItemFunction<T> =

export type ListGraphQLConfig = {
// was previously top-level cacheHint
// cacheHint?: CacheHint;
cacheHint?: ((args: any) => CacheHint) | CacheHint;
/**
* The description added to the GraphQL schema
* @default listConfig.description
Expand All @@ -232,5 +238,3 @@ export type ListDBConfig = {
*/
searchField?: string;
};

// export type CacheHint = { scope: 'PRIVATE' | 'PUBLIC'; maxAge: number };
8 changes: 7 additions & 1 deletion packages-next/types/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IncomingMessage, ServerResponse } from 'http';
import type { GraphQLResolveInfo } from 'graphql';
import type { GqlNames, MaybePromise } from './utils';
import type { KeystoneContext, SessionContext } from './context';

Expand All @@ -24,7 +25,12 @@ export type SessionImplementation = {
): Promise<SessionContext<any>>;
};

export type GraphQLResolver = (root: any, args: any, context: KeystoneContext) => any;
export type GraphQLResolver = (
root: any,
args: any,
context: KeystoneContext,
info: GraphQLResolveInfo
) => any;

export type GraphQLSchemaExtension = {
typeDefs: string;
Expand Down
1 change: 1 addition & 0 deletions tests/api-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@keystone-next/test-utils-legacy": "^14.0.0",
"@keystone-next/types": "^15.0.0",
"@keystone-next/utils-legacy": "^7.0.0",
"apollo-cache-control": "^0.11.6",
"express": "^4.17.1"
}
}
Original file line number Diff line number Diff line change
@@ -1,93 +1,91 @@
const { Integer, Text, Relationship } = require('@keystone-next/fields-legacy');
const {
import { CacheScope } from 'apollo-cache-control';
import { text, relationship, integer } from '@keystone-next/fields';
import {
multiAdapterRunners,
setupServer,
setupFromConfig,
networkedGraphqlRequest,
} = require('@keystone-next/test-utils-legacy');
const { createItems } = require('@keystone-next/server-side-graphql-client-legacy');

function setupKeystone(adapterName) {
return setupServer({
testConfig,
AdapterName,
} from '@keystone-next/test-utils-legacy';
// @ts-ignore
import { createItems } from '@keystone-next/server-side-graphql-client-legacy';
import { list, createSchema, graphQLSchemaExtension } from '@keystone-next/keystone/schema';
import { KeystoneContext } from '@keystone-next/types';

function setupKeystone(adapterName: AdapterName) {
return setupFromConfig({
adapterName,
createLists: keystone => {
keystone.createList('Post', {
fields: {
title: { type: Text },
author: { type: Relationship, ref: 'User.posts', many: true },
},
cacheHint: {
scope: 'PUBLIC',
maxAge: 100,
},
});

keystone.createList('User', {
fields: {
name: {
type: Text,
cacheHint: {
maxAge: 80,
},
config: testConfig({
lists: createSchema({
Post: list({
fields: {
title: text(),
author: relationship({ ref: 'User.posts', many: true }),
},
favNumber: {
type: Integer,
cacheHint: {
maxAge: 10,
scope: 'PRIVATE',
graphql: {
cacheHint: { scope: CacheScope.Public, maxAge: 100 },
},
}),
User: list({
fields: {
name: text({
graphql: { cacheHint: { maxAge: 80 } },
}),
favNumber: integer({
graphql: { cacheHint: { maxAge: 10, scope: CacheScope.Private } },
}),
posts: relationship({ ref: 'Post.author', many: true }),
},
graphql: {
cacheHint: ({ results, operationName, meta }) => {
if (meta) {
return { scope: CacheScope.Public, maxAge: 90 };
}
if (operationName === 'complexQuery') {
return { maxAge: 1 };
}
if (results.length === 0) {
return { maxAge: 5 };
}
return { maxAge: 100 };
},
},
posts: { type: Relationship, ref: 'Post.author', many: true },
},
cacheHint: ({ results, operationName, meta }) => {
if (meta) {
return {
scope: 'PUBLIC',
maxAge: 90,
};
}),
}),
extendGraphqlSchema: graphQLSchemaExtension({
typeDefs: `
type MyType {
original: Int
double: Float
}
if (operationName === 'complexQuery') {
return {
maxAge: 1,
};
type Mutation {
triple(x: Int): Int
}
if (results.length === 0) {
return {
maxAge: 5,
};
type Query {
double(x: Int): MyType
}
return {
maxAge: 100,
};
`,
resolvers: {
Query: {
double: (root, { x }, context, info) => {
info.cacheControl.setCacheHint({ scope: CacheScope.Public, maxAge: 100 });
return { original: x, double: 2.0 * x };
},
},
Mutation: {
triple: (root, { x }) => 3 * x,
},
},
});

// These should be added to the system and tested when we implement cacheHints
// keystone.extendGraphQLSchema({
// types: [{ type: 'type MyType { original: Int, double: Float }' }],
// queries: [
// {
// schema: 'double(x: Int): MyType',
// resolver: (_, { x }) => ({ original: x, double: 2.0 * x }),
// cacheHint: {
// scope: 'PUBLIC',
// maxAge: 100,
// },
// },
// ],
// mutations: [
// {
// schema: 'triple(x: Int): Int',
// resolver: (_, { x }) => 3 * x,
// },
// ],
// });
},
}),
}),
});
}

const addFixtures = async keystone => {
const addFixtures = async (context: KeystoneContext) => {
const users = await createItems({
keystone,
context,
listKey: 'User',
items: [
{ data: { name: 'Jess', favNumber: 1 } },
Expand All @@ -97,7 +95,7 @@ const addFixtures = async keystone => {
});

const posts = await createItems({
keystone,
context,
listKey: 'Post',
items: [
{ data: { author: { connect: [{ id: users[0].id }] }, title: 'One author' } },
Expand All @@ -124,8 +122,8 @@ multiAdapterRunners().map(({ runner, adapterName }) =>
describe('cache hints', () => {
test(
'users',
runner(setupKeystone, async ({ keystone, app }) => {
await addFixtures(keystone);
runner(setupKeystone, async ({ context, app }) => {
await addFixtures(context);

// Basic query
let { data, errors, res } = await networkedGraphqlRequest({
Expand Down Expand Up @@ -230,8 +228,8 @@ multiAdapterRunners().map(({ runner, adapterName }) =>

test(
'posts',
runner(setupKeystone, async ({ keystone, app }) => {
await addFixtures(keystone);
runner(setupKeystone, async ({ context, app }) => {
await addFixtures(context);
// The Post list has a static cache hint

// Basic query
Expand Down Expand Up @@ -327,8 +325,8 @@ multiAdapterRunners().map(({ runner, adapterName }) =>

test(
'mutations',
runner(setupKeystone, async ({ keystone, app }) => {
const { posts } = await addFixtures(keystone);
runner(setupKeystone, async ({ context, app }) => {
const { posts } = await addFixtures(context);

// Mutation responses shouldn't be cached.
// Here's a smoke test to make sure they still work.
Expand All @@ -354,8 +352,8 @@ multiAdapterRunners().map(({ runner, adapterName }) =>
// eslint-disable-next-line jest/no-disabled-tests
test.skip(
'extendGraphQLSchemaQueries',
runner(setupKeystone, async ({ keystone, app }) => {
await addFixtures(keystone);
runner(setupKeystone, async ({ context, app }) => {
await addFixtures(context);

// Basic query
let { data, errors, res } = await networkedGraphqlRequest({
Expand All @@ -380,8 +378,8 @@ multiAdapterRunners().map(({ runner, adapterName }) =>
// eslint-disable-next-line jest/no-disabled-tests
test.skip(
'extendGraphQLSchemaMutations',
runner(setupKeystone, async ({ keystone, app }) => {
await addFixtures(keystone);
runner(setupKeystone, async ({ context, app }) => {
await addFixtures(context);

// Mutation responses shouldn't be cached.
// Here's a smoke test to make sure they still work.
Expand Down

0 comments on commit 6321f49

Please sign in to comment.