From 38bb939b8ade0e1ca3d532ba36428d2dc5c53883 Mon Sep 17 00:00:00 2001 From: Ethan Veres Date: Tue, 4 Aug 2020 13:53:57 -0400 Subject: [PATCH] fix: optional id (#339) * fix: optional id * infer id --- src/resources/HttpResource.ts | 2 +- src/resources/Resource.ts | 8 ++------ src/types/createResolve.ts | 17 ++++++++++++----- test/HttpApi.test.ts | 8 ++++---- test/HttpResource.test.ts | 2 +- test/NodeType.test.ts | 23 +++++++++++++---------- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/resources/HttpResource.ts b/src/resources/HttpResource.ts index 1699cf25..b3b5b2b1 100644 --- a/src/resources/HttpResource.ts +++ b/src/resources/HttpResource.ts @@ -20,7 +20,7 @@ export default class HttpResource< > extends Resource { readonly api: TApi; - _endpoint: Endpoint; + protected readonly _endpoint: Endpoint; constructor(context: TContext, { endpoint }: HttpResourceOptions) { super(context); diff --git a/src/resources/Resource.ts b/src/resources/Resource.ts index 2abf3a91..e45f597a 100644 --- a/src/resources/Resource.ts +++ b/src/resources/Resource.ts @@ -1,11 +1,7 @@ import { Maybe, Obj } from '../utils/typing'; export default abstract class Resource { - context: TContext; + protected constructor(public readonly context: TContext) {} - constructor(context: TContext) { - this.context = context; - } - - abstract get(id: string): Promise>; + abstract get(id?: string): Promise>; } diff --git a/src/types/createResolve.ts b/src/types/createResolve.ts index 6c18d968..b3dc117a 100644 --- a/src/types/createResolve.ts +++ b/src/types/createResolve.ts @@ -2,12 +2,19 @@ import { GraphQLFieldConfig, GraphQLFieldResolver } from 'graphql'; import NodeType from './NodeType'; -export default function createResolve( - resolve: GraphQLFieldResolver, TContext>, - fieldNames: TFields[], -): GraphQLFieldConfig['resolve'] { +export default function createResolve< + TSource, + TContext, + TField extends string +>( + resolve: GraphQLFieldResolver, TContext>, + fieldNames: TField[], +): GraphQLFieldConfig, TContext>['resolve'] { return async (obj, args, context, info) => { - const nodeType = info.parentType as NodeType>; + const nodeType = info.parentType as NodeType< + any, + TSource & Record + >; for (const fieldName of fieldNames) { if (obj[fieldName] === undefined) { // await in a loop is OK here. dataloader makes sure that only diff --git a/test/HttpApi.test.ts b/test/HttpApi.test.ts index 13c05518..2717fe0f 100644 --- a/test/HttpApi.test.ts +++ b/test/HttpApi.test.ts @@ -83,11 +83,11 @@ describe('HttpApi', () => { const keys = range(api.numKeysPerChunk + 5).map(String); - const params = keys.map(key => `saladId=${key}`); + const params = keys.map((key) => `saladId=${key}`); const chunk1Params = params.slice(0, api.numKeysPerChunk); const chunk2Params = params.slice(api.numKeysPerChunk); - const data = keys.map(saladId => ({ saladId })); + const data = keys.map((saladId) => ({ saladId })); const chunk1Data = data.slice(0, api.numKeysPerChunk); const chunk2Data = data.slice(api.numKeysPerChunk); @@ -102,8 +102,8 @@ describe('HttpApi', () => { const loader = api.createArgLoader('dressings', 'saladId'); - expect(await Promise.all(keys.map(key => loader.load(key)))).toEqual( - data.map(item => [item]), + expect(await Promise.all(keys.map((key) => loader.load(key)))).toEqual( + data.map((item) => [item]), ); }); }); diff --git a/test/HttpResource.test.ts b/test/HttpResource.test.ts index 2a7ea7a6..3f91f482 100644 --- a/test/HttpResource.test.ts +++ b/test/HttpResource.test.ts @@ -19,7 +19,7 @@ describe('HttpResource', () => { it('should accept a function as the endpoint', () => { const resource = new HttpResource(mockContext, { - endpoint: id => `salads/${id || ''}`, + endpoint: (id) => `salads/${id || ''}`, }); expect(resource.getPath('1')).toEqual('salads/1'); diff --git a/test/NodeType.test.ts b/test/NodeType.test.ts index dbcaa6fe..e12e78cf 100644 --- a/test/NodeType.test.ts +++ b/test/NodeType.test.ts @@ -9,8 +9,8 @@ import mockedFetch from 'node-fetch'; import { HttpResource } from '../src'; import { getConfig, setup } from '../src/config'; -import createResolve from '../src/types/createResolve'; import NodeType from '../src/types/NodeType'; +import createResolve from '../src/types/createResolve'; import { MockContext, TestHttpApi, TestHttpResource } from './helpers'; describe('NodeType', () => { @@ -63,19 +63,21 @@ describe('NodeType', () => { name: { type: GraphQLString }, resolvedFavoriteColor: { type: GraphQLString, - resolve: createResolve(obj => obj.favoriteColor, ['favoriteColor']), + resolve: createResolve((obj) => obj.favoriteColor, [ + 'favoriteColor', + ]), }, resolvedUserId: { type: GraphQLString, - resolve: o => o.id, + resolve: (o) => o.id, }, }), - makeId: a => a.id, - createResource: ctx => new WidgetResource(ctx, { endpoint: 'users' }), + makeId: (a) => a.id, + createResource: (ctx) => new WidgetResource(ctx, { endpoint: 'users' }), localIdFieldName: 'userId', }); - const Widget = new NodeType({ + const Widget: NodeType = new NodeType({ name: 'Widget', fields: () => ({ name: { type: GraphQLString }, @@ -88,7 +90,8 @@ describe('NodeType', () => { type: User, }, }), - createResource: ctx => new WidgetResource(ctx, { endpoint: 'widgets' }), + createResource: (ctx) => + new WidgetResource(ctx, { endpoint: 'widgets' }), }); schema = new GraphQLSchema({ @@ -188,7 +191,7 @@ describe('NodeType', () => { }); it('should createNode', () => { - const type = new NodeType({ + const type: NodeType> = new NodeType({ name: 'Foo', fields: () => ({ foo: { @@ -202,7 +205,7 @@ describe('NodeType', () => { ), }, }), - createResource: ctx => new HttpResource(ctx, { endpoint: 'foo/bar' }), + createResource: (ctx) => new HttpResource(ctx, { endpoint: 'foo/bar' }), }); expect(type.name).toEqual('Foo'); @@ -214,7 +217,7 @@ describe('NodeType', () => { fields: () => ({ foo: { type: GraphQLString }, }), - createResource: ctx => new HttpResource(ctx, { endpoint: 'foo/bar' }), + createResource: (ctx) => new HttpResource(ctx, { endpoint: 'foo/bar' }), makeId: ({ foo, bar }) => `${foo}/${bar}`, });