From 09f87c7a0bf3a4cbcfaf3c2e3f95214fc8dd66ab Mon Sep 17 00:00:00 2001 From: Hyeseong Kim Date: Sun, 17 May 2020 20:12:47 +0900 Subject: [PATCH] Fix type definition of t to infer resolver shape correctly --- .vscode/settings.json | 7 +--- package.json | 3 +- src/examples/with-graphql-tools/index.ts | 4 +- src/examples/with-nexus/index.ts | 8 +++- src/index.ts | 43 +++++++++++--------- src/types/resolvers.ts | 17 ++++---- src/types/t.ts | 51 ++++++++++-------------- yarn.lock | 5 --- 8 files changed, 64 insertions(+), 74 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 98f920a..6072d1f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,9 @@ { "typescript.tsdk": "node_modules/typescript/lib", - "typescript.format.enable": false, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "editor.formatOnSave": true, "importSorter.generalConfiguration.sortOnBeforeSave": true, "importSorter.importStringConfiguration.hasSemicolon": false, "importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.type": "newLineEachExpressionAfterCountLimitExceptIfOnlyOne", "importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.count": 80, "importSorter.importStringConfiguration.tabSize": 2, "importSorter.importStringConfiguration.quoteMark": "single" -} +} \ No newline at end of file diff --git a/package.json b/package.json index f6a72e4..51c6549 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "dataloader": "^2.0.0", "graphql": "^14.5.8", + "graphql-middleware": "^4.0.2", "memcached": "^2.2.2" }, "devDependencies": { @@ -39,10 +40,8 @@ "@nexus/schema": "^0.13.1", "@types/memcached": "^2.2.6", "apollo-server": "^2.13.1", - "graphql-middleware": "^4.0.2", "graphql-tools": "^5.0.0", "link-module-alias": "^1.2.0", - "prettier": "^2.0.5", "rimraf": "^3.0.2", "tsc-watch": "^4.2.5", "typescript": "^3.9.2" diff --git a/src/examples/with-graphql-tools/index.ts b/src/examples/with-graphql-tools/index.ts index 29dd0a0..79023e2 100644 --- a/src/examples/with-graphql-tools/index.ts +++ b/src/examples/with-graphql-tools/index.ts @@ -1,6 +1,6 @@ import { ApolloServer } from 'apollo-server' import fs from 'fs' -import { cached } from 'graphql-cached' +import { cached } from '../../index' import { applyMiddleware } from 'graphql-middleware' import { makeExecutableSchema } from 'graphql-tools' import Memcached from 'memcached' @@ -34,7 +34,7 @@ const cachedSchema = applyMiddleware( Query: { user: { lifetime: 10, - key(_parent: any, args: any) { + key(parent, args) { return JSON.stringify(args) }, }, diff --git a/src/examples/with-nexus/index.ts b/src/examples/with-nexus/index.ts index ff2e0fa..b7b49d7 100644 --- a/src/examples/with-nexus/index.ts +++ b/src/examples/with-nexus/index.ts @@ -1,5 +1,5 @@ import { ApolloServer } from 'apollo-server' -import { cached } from 'graphql-cached' +import { cached } from '../../index' import { applyMiddleware } from 'graphql-middleware' import Memcached from 'memcached' import path from 'path' @@ -46,16 +46,20 @@ const cachedSchema = applyMiddleware( cached>( { Query: { + // @ts-ignore user: { lifetime: 10, - key(_parent: any, args: any) { + // @ts-ignore + key(_parent, args) { return JSON.stringify(args) }, }, }, User: { + // @ts-ignore image: { lifetime: 1, + // @ts-ignore key(parent) { return parent.imageId }, diff --git a/src/index.ts b/src/index.ts index 593460e..2979fe6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,16 +2,15 @@ import DataLoader from 'dataloader' import { GraphQLResolveInfo } from 'graphql' import { promisify } from 'util' -import { CacheField, Config, ResolversBase, T } from './types' +import type { Config, T, ResolverLike, CacheField } from './types' +import type { IMiddlewareTypeMap } from 'graphql-middleware' const DEFAULT_LIFETIME = 10 -export function cached( - t: T>, +export function cached( + t: T, config: Config -) { - const _t: any = t - +): IMiddlewareTypeMap { /** * Initialize cache getter(loader), setter */ @@ -28,17 +27,19 @@ export function cached( } ) - const resolveWithCache = async ( - resolve: any, - parent: any, - args: any, + const resolveWithCache = async( + resolve: ResolverLike, + parent: Parent, + args: Arg, context: Context, info: GraphQLResolveInfo - ) => { - const _typeName = info.parentType.toString() - const _fieldName = info.fieldName + ): Promise => { + const _typeName = info.parentType.toString() as keyof typeof t; + const _type = t[_typeName]; + + const _fieldName = info.fieldName as keyof typeof _type; + const _field = _type[_fieldName] as CacheField; - const _field: CacheField = _t[_typeName][_fieldName] const _fieldKey = 'key' in _field ? _field.key : _field const _fieldSerializer = 'serializer' in _field ? _field.serializer : null const _fieldLifetime = 'lifetime' in _field ? _field.lifetime : null @@ -64,7 +65,7 @@ export function cached( * Get cache from cache storage */ config.beforeGet?.(key, null) - const cachedItem = (await cacheLoader.load(key)) || null + const cachedItem = (await cacheLoader.load(key) as unknown as Type) || null config.afterGet?.(key, cachedItem) if (cachedItem) { @@ -104,12 +105,16 @@ export function cached( } } - const resolvers: any = {} + const resolvers: any = {}; - for (const typeName of Object.keys(_t)) { - resolvers[typeName] = {} + for (const typeName of Object.keys(t)) { + resolvers[typeName] = {}; - for (const fieldName of Object.keys(_t[typeName])) { + const _type = t[typeName as keyof typeof t]; + if (!_type) { + continue; + } + for (const fieldName of Object.keys(_type as NonNullable)) { resolvers[typeName][fieldName] = resolveWithCache } } diff --git a/src/types/resolvers.ts b/src/types/resolvers.ts index 3e89fae..a8d0e67 100644 --- a/src/types/resolvers.ts +++ b/src/types/resolvers.ts @@ -1,9 +1,8 @@ -export interface ResolversBase { - [typeName: string]: - | { - [fieldName: string]: - | ((parent: any, args: any, context: any, info: any) => any) - | undefined - } - | undefined -} +import type { GraphQLResolveInfo } from "graphql"; + +export type ResolverLike = ( + root: Parent, + args: Arg, + context: Context, + info: GraphQLResolveInfo +) => Type | Promise; diff --git a/src/types/t.ts b/src/types/t.ts index 983e92a..bf18e02 100644 --- a/src/types/t.ts +++ b/src/types/t.ts @@ -1,57 +1,50 @@ -import { GraphQLResolveInfo } from 'graphql' +import type { ResolverLike } from "./resolvers"; -export type T = { - [TypeName in keyof RequiredResolvers]?: { - [FieldName in keyof RequiredResolvers[TypeName]]?: CacheField< - Parameters[0], - Parameters[1], - Parameters[2] - > - } -} +export type T = ( + Resolvers extends { [key: string]: infer _ } + ? { [P in keyof Resolvers]?: T } + : Resolvers extends ResolverLike + ? CacheField + : never +); -export type CacheField = +export type CacheField = ( | CacheFieldKey | { - /** + /** * Create a cache key by combining parent, args, context and info. * @param {Object} parent * @param {Object} args * @param {Object} context * @param {Object} info */ - key: CacheFieldKey + key: CacheFieldKey; - /** + /** * How much time to keep the cache (seconds) */ - lifetime?: number + lifetime?: number; - /** + /** * Preprocess item before storing in cache and after fetching from cache */ - serializer?: CacheFieldSerializer - } - -export type CacheFieldKey = ( - parent: Parent, - args: Arg, - context: Context, - info: GraphQLResolveInfo -) => string + serializer?: CacheFieldSerializer; + } +); -export type CacheFieldSerializer = { +export type CacheFieldKey = ResolverLike; +export type CacheFieldSerializer = { /** * Preprocess item before storing it in the cache * @param {Object} item * @returns {Object} Serialized item */ - serialize(item: any): any + serialize(item: Type): any; /** * Preprocess item after fetching from cache * @param {Object} Serialized item * @returns {Object} Item */ - deserialize(serializedItem: any): any -} + deserialize(serializedItem: any): Type; +}; diff --git a/yarn.lock b/yarn.lock index b253f3a..bf6a2ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3959,11 +3959,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -prettier@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" - integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== - prisma-json-schema@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/prisma-json-schema/-/prisma-json-schema-0.1.3.tgz#6c302db8f464f8b92e8694d3f7dd3f41ac9afcbe"