From 5d93245998f72daa61cb8e176c09a51e2478d627 Mon Sep 17 00:00:00 2001 From: Dan Caddigan Date: Sat, 1 Jan 2022 14:44:44 -0500 Subject: [PATCH] fix(one-to-many): one to manys should not be nullable (#479) --- package.json | 1 + src/core/BaseService.ts | 40 +++++++++++++++---- src/decorators/OneToMany.ts | 3 +- src/decorators/debug.ts | 5 ++- .../__snapshots__/schema.test.ts.snap | 2 +- src/test/generated/binding.ts | 2 +- src/test/generated/schema.graphql | 2 +- src/test/utils.ts | 5 ++- yarn.lock | 5 +++ 9 files changed, 48 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 90172cce..36851115 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "debug": "^4.1.1", "execa": "^4.0.3", "express": "^4.17.1", + "flatted": "^3.2.4", "gluegun": "^4.1.0", "graphql": "^14.5.8", "graphql-binding": "^2.5.2", diff --git a/src/core/BaseService.ts b/src/core/BaseService.ts index 51a64095..1deb8c08 100644 --- a/src/core/BaseService.ts +++ b/src/core/BaseService.ts @@ -146,14 +146,20 @@ export class BaseService { orderBy?: string, limit?: number, offset?: number, - fields?: string[] + fields?: string[], + userId?: string, + options?: BaseOptionsExtended ): Promise { + const manager = this.extractManager(options); + const DEFAULT_LIMIT = 50; return this.buildFindQuery( where as WhereExpression, orderBy, { limit: limit || DEFAULT_LIMIT, offset }, - fields + fields, + userId, + { manager } ).getMany(); } @@ -162,8 +168,12 @@ export class BaseService { whereUserInput: any = {}, // V3: WhereExpression = {}, orderBy?: string | string[], _pageOptions: RelayPageOptionsInput = {}, - fields?: ConnectionInputFields + fields?: ConnectionInputFields, + userId?: string, // Allow this param so that when we overload we can pass the userId for filtering data to user's "world" + options?: BaseOptionsExtended ): Promise> { + const manager = this.extractManager(options); + // TODO: if the orderby items aren't included in `fields`, should we automatically include? // TODO: FEATURE - make the default limit configurable const DEFAULT_LIMIT = 50; @@ -202,7 +212,9 @@ export class BaseService { whereCombined, this.relayService.effectiveOrderStrings(sorts, relayPageOptions), { limit: limit + 1 }, // We ask for 1 too many so that we know if there is an additional page - requestedFields.selectFields + requestedFields.selectFields, + userId, + { manager } ); let rawData; @@ -235,10 +247,14 @@ export class BaseService { where: any = {}, // W | WhereExpression orderBy?: string | string[], pageOptions?: LimitOffset, - fields?: string[] + fields?: string[], + userId?: string, // Allow this param so that when we overload we can pass the userId for filtering data to user's "world" + options?: BaseOptionsExtended ): SelectQueryBuilder { + const manager = this.extractManager(options); + const DEFAULT_LIMIT = 50; - let qb = this.manager.createQueryBuilder(this.entityClass, this.klass); + let qb = manager.createQueryBuilder(this.entityClass, this.klass); if (!pageOptions) { pageOptions = { limit: DEFAULT_LIMIT @@ -393,8 +409,16 @@ export class BaseService { return qb; } - async findOne(where: W): Promise { - const items = await this.find(where as any); + async findOne(where: W, userId?: string, options?: BaseOptionsExtended): Promise { + const items = await this.find( + where as any, + undefined, + undefined, + undefined, + undefined, + userId, + options + ); if (!items.length) { throw new Error(`Unable to find ${this.entityClass.name} where ${JSON.stringify(where)}`); } else if (items.length > 1) { diff --git a/src/decorators/OneToMany.ts b/src/decorators/OneToMany.ts index 70f03379..80fe78e8 100644 --- a/src/decorators/OneToMany.ts +++ b/src/decorators/OneToMany.ts @@ -1,11 +1,10 @@ import { Field } from 'type-graphql'; import { OneToMany as TypeORMOneToMany } from 'typeorm'; - import { composeMethodDecorators, MethodDecoratorFactory } from '../utils'; export function OneToMany(parentType: any, joinFunc: any, options: any = {}): any { const factories = [ - Field(parentType, { nullable: true, ...options }) as MethodDecoratorFactory, + Field(parentType, { ...options }) as MethodDecoratorFactory, TypeORMOneToMany(parentType, joinFunc) as MethodDecoratorFactory ]; diff --git a/src/decorators/debug.ts b/src/decorators/debug.ts index b228d00a..9f298ed8 100644 --- a/src/decorators/debug.ts +++ b/src/decorators/debug.ts @@ -1,4 +1,5 @@ import * as Debug from 'debug'; +import { stringify } from 'flatted'; import { performance } from 'perf_hooks'; import * as util from 'util'; @@ -12,7 +13,7 @@ export function debug(key: string): MethodDecorator { if (util.types.isAsyncFunction(originalMethod)) { descriptor.value = async function(...args: unknown[]): Promise { - logger(`Entering ${propertyKey} with args: ${JSON.stringify(args)}`); + logger(`Entering ${propertyKey} with args: ${stringify(args)}`); const start = performance.now(); const result = await originalMethod.apply(this, args); const end = performance.now(); @@ -21,7 +22,7 @@ export function debug(key: string): MethodDecorator { }; } else { descriptor.value = function(...args: unknown[]) { - logger(`Entering ${propertyKey} with args: ${JSON.stringify(args)}`); + logger(`Entering ${propertyKey} with args: ${stringify(args)}`); const start = performance.now(); const result = originalMethod.apply(this, args); const end = performance.now(); diff --git a/src/test/functional/__snapshots__/schema.test.ts.snap b/src/test/functional/__snapshots__/schema.test.ts.snap index e55144c3..b2dbafec 100644 --- a/src/test/functional/__snapshots__/schema.test.ts.snap +++ b/src/test/functional/__snapshots__/schema.test.ts.snap @@ -144,7 +144,7 @@ type KitchenSink { jsonField: JSONObject idField: ID stringEnumField: StringEnum - dishes: [Dish!] + dishes: [Dish!]! numericField: Float numericFieldCustomPrecisionScale: Float noFilterField: String diff --git a/src/test/generated/binding.ts b/src/test/generated/binding.ts index c866c4e1..0114e70c 100644 --- a/src/test/generated/binding.ts +++ b/src/test/generated/binding.ts @@ -450,7 +450,7 @@ export interface KitchenSink { jsonField?: JSONObject | null idField?: ID_Output | null stringEnumField?: StringEnum | null - dishes?: Array | null + dishes: Array numericField?: Float | null numericFieldCustomPrecisionScale?: Float | null noFilterField?: String | null diff --git a/src/test/generated/schema.graphql b/src/test/generated/schema.graphql index 8a293bf5..e30a668d 100644 --- a/src/test/generated/schema.graphql +++ b/src/test/generated/schema.graphql @@ -141,7 +141,7 @@ type KitchenSink { jsonField: JSONObject idField: ID stringEnumField: StringEnum - dishes: [Dish!] + dishes: [Dish!]! numericField: Float numericFieldCustomPrecisionScale: Float noFilterField: String diff --git a/src/test/utils.ts b/src/test/utils.ts index 91dba6bc..323937df 100644 --- a/src/test/utils.ts +++ b/src/test/utils.ts @@ -1,3 +1,4 @@ +import { stringify } from 'flatted'; import { GraphQLError } from 'graphql'; import { getBindingError } from '../'; @@ -78,9 +79,9 @@ function toErrorString(obj: unknown, msg: string): string { if (typeof obj === 'string') { error = `${msg}: ${obj}`; } else { - error = `${msg}: ${JSON.stringify(obj)}`; + error = `${msg}: ${stringify(obj)}`; } - console.dir(obj); + console.error(); return error; } diff --git a/yarn.lock b/yarn.lock index df92c409..16e85d88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3790,6 +3790,11 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== +flatted@^3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"