From ca22bb0235462835777f0e727af2dc0cfbfd8bc7 Mon Sep 17 00:00:00 2001 From: Flavian DESVERNE Date: Mon, 14 Oct 2019 19:35:33 +0200 Subject: [PATCH] fix: generated publishers are now all camel-cased BREAKING CHANGE: publishers accessed using old lower-case naming will break --- package.json | 1 + src/dmmf/DMMFClass.ts | 2 +- src/naming-strategies.ts | 5 +- tests/__snapshots__/naming.test.ts.snap | 341 ++++++++++++++++++++++++ tests/naming.test.ts | 45 ++++ 5 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 tests/__snapshots__/naming.test.ts.snap create mode 100644 tests/naming.test.ts diff --git a/package.json b/package.json index d8096ae91..16716a425 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@prisma/photon": "^0.2.94", + "camelcase": "^5.3.1", "fs-extra": "^8.1.0", "nexus": "^0.12.0-beta.12", "pluralize": "^8.0.0" diff --git a/src/dmmf/DMMFClass.ts b/src/dmmf/DMMFClass.ts index 8fe2ac2b5..01709dcb8 100644 --- a/src/dmmf/DMMFClass.ts +++ b/src/dmmf/DMMFClass.ts @@ -128,7 +128,7 @@ export class OutputType { if (!field) { throw new Error( - `Could not find field field '${fieldName}' on type ${this.outputType.name}`, + `Could not find field '${this.outputType.name}.${fieldName}' on type ${this.outputType.name}`, ) } diff --git a/src/naming-strategies.ts b/src/naming-strategies.ts index 58872b531..13d09525a 100644 --- a/src/naming-strategies.ts +++ b/src/naming-strategies.ts @@ -1,6 +1,7 @@ import pluralize from 'pluralize' import * as DMMF from './dmmf' import { upperFirst } from './utils' +import camelCase from 'camelcase' export interface ArgsNamingStrategy { whereInput: (typeName: string, fieldName: string) => string @@ -28,8 +29,8 @@ export type FieldNamingStrategy = Record< > export const defaultFieldNamingStrategy: FieldNamingStrategy = { - findOne: (_, modelName) => modelName.toLowerCase(), - findMany: (_, modelName) => pluralize(modelName).toLowerCase(), + findOne: (_, modelName) => camelCase(modelName), + findMany: (_, modelName) => camelCase(pluralize(modelName)), create: fieldName => fieldName, update: fieldName => fieldName, delete: fieldName => fieldName, diff --git a/tests/__snapshots__/naming.test.ts.snap b/tests/__snapshots__/naming.test.ts.snap new file mode 100644 index 000000000..20a92d7ec --- /dev/null +++ b/tests/__snapshots__/naming.test.ts.snap @@ -0,0 +1,341 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generates publishers pluralized & camel-cased: schema 1`] = ` +"type BatchPayload { + count: Int! +} + +input IntFilter { + equals: Int + not: Int + in: [Int!] + notIn: [Int!] + lt: Int + lte: Int + gt: Int + gte: Int +} + +type ModelName { + id: Int! +} + +input ModelNameCreateInput { + name: String! +} + +input ModelNameUpdateInput { + id: Int + name: String +} + +input ModelNameUpdateManyMutationInput { + id: Int + name: String +} + +input ModelNameWhereInput { + id: IntFilter + name: StringFilter + AND: [ModelNameWhereInput!] + OR: [ModelNameWhereInput!] + NOT: [ModelNameWhereInput!] +} + +input ModelNameWhereUniqueInput { + id: Int +} + +type Mutation { + createOneModelName(data: ModelNameCreateInput!): ModelName! + deleteOneModelName(where: ModelNameWhereUniqueInput!): ModelName + updateOneModelName(data: ModelNameUpdateInput!, where: ModelNameWhereUniqueInput!): ModelName + upsertOneModelName(where: ModelNameWhereUniqueInput!, create: ModelNameCreateInput!, update: ModelNameUpdateInput!): ModelName! + updateManyModelName(data: ModelNameUpdateManyMutationInput!, where: ModelNameWhereInput): BatchPayload! +} + +type Query { + modelName(where: ModelNameWhereUniqueInput!): ModelName + modelNames(skip: Int, after: String, before: String, first: Int, last: Int): [ModelName!]! +} + +input StringFilter { + equals: String + not: String + in: [String!] + notIn: [String!] + lt: String + lte: String + gt: String + gte: String + contains: String + startsWith: String + endsWith: String +} +" +`; + +exports[`generates publishers pluralized & camel-cased: typegen 1`] = ` +"import * as photon from '@generated/photon'; +import { core } from 'nexus'; +// Types helpers + type IsModelNameExistsInGraphQLTypes< + ReturnType extends any +> = ReturnType extends core.GetGen<'objectNames'> ? true : false; + +type NexusPrismaScalarOpts = { + alias?: string; +}; + +type Pagination = { + first?: boolean; + last?: boolean; + before?: boolean; + after?: boolean; + skip?: boolean; +}; + +type RootObjectTypes = Pick< + core.GetGen<'rootTypes'>, + core.GetGen<'objectNames'> +>; + +/** + * Determine if \`B\` is a subset (or equivalent to) of \`A\`. +*/ +type IsSubset = keyof A extends never + ? false + : B extends A + ? true + : false; + +type OmitByValue = Pick< + T, + { [Key in keyof T]: T[Key] extends ValueType ? never : Key }[keyof T] +>; + +type GetSubsetTypes = keyof OmitByValue< + { + [P in keyof RootObjectTypes]: ModelName extends keyof ModelTypes + ? IsSubset extends true + ? RootObjectTypes[P] + : never + : never; + }, + never +>; + +type SubsetTypes = GetSubsetTypes< + ModelName +> extends never + ? \`ERROR: No subset types are available. Please make sure that one of your GraphQL type is a subset of your t.model('')\` + : GetSubsetTypes; + +type DynamicRequiredType = IsModelNameExistsInGraphQLTypes< + ReturnType +> extends true + ? { type?: SubsetTypes } + : { type: SubsetTypes }; + +type GetNexusPrismaInput< + ModelName extends any, + MethodName extends any, + InputName extends 'filtering' | 'ordering' +> = ModelName extends keyof NexusPrismaInputs + ? MethodName extends keyof NexusPrismaInputs[ModelName] + ? NexusPrismaInputs[ModelName][MethodName][InputName] + : never + : never; + +type NexusPrismaRelationOpts< + ModelName extends any, + MethodName extends any, + ReturnType extends any +> = GetNexusPrismaInput< + // If GetNexusPrismaInput returns never, it means there are no filtering/ordering args for it. So just use \`alias\` and \`type\` + ModelName, + MethodName, + 'filtering' +> extends never + ? { + alias?: string; + } & DynamicRequiredType + : { + alias?: string; + filtering?: + | boolean + | Partial< + Record< + GetNexusPrismaInput, + boolean + > + >; + ordering?: + | boolean + | Partial< + Record< + GetNexusPrismaInput, + boolean + > + >; + pagination?: boolean | Pagination; + } & DynamicRequiredType; + +type IsScalar = TypeName extends core.GetGen<'scalarNames'> + ? true + : false; + +type IsObject = Name extends core.GetGen<'objectNames'> + ? true + : false + +type IsEnum = Name extends core.GetGen<'enumNames'> + ? true + : false + +type IsInputObject = Name extends core.GetGen<'inputNames'> + ? true + : false + +/** + * The kind that a GraphQL type may be. + */ +type Kind = 'Enum' | 'Object' | 'Scalar' | 'InputObject' + +/** + * Helper to safely reference a Kind type. For example instead of the following + * which would admit a typo: + * + * \`\`\`ts + * type Foo = Bar extends 'scalar' ? ... + * \`\`\` + * + * You can do this which guarantees a correct reference: + * + * \`\`\`ts + * type Foo = Bar extends AKind<'Scalar'> ? ... + * \`\`\` + * + */ +type AKind = T + +type GetKind = IsEnum extends true + ? 'Enum' + : IsScalar extends true + ? 'Scalar' + : IsObject extends true + ? 'Object' + : IsInputObject extends true + ? 'InputObject' + // FIXME should be \`never\`, but GQL objects named differently + // than backing type fall into this branch + : 'Object' + +type NexusPrismaFields = { + [MethodName in keyof NexusPrismaTypes[ModelName]]: NexusPrismaMethod< + ModelName, + MethodName, + GetKind // Is the return type a scalar? + >; +}; + +type NexusPrismaMethod< + ModelName extends keyof NexusPrismaTypes, + MethodName extends keyof NexusPrismaTypes[ModelName], + ThisKind extends Kind, + ReturnType extends any = NexusPrismaTypes[ModelName][MethodName] +> = + ThisKind extends AKind<'Enum'> + ? () => NexusPrismaFields + : ThisKind extends AKind<'Scalar'> + ? (opts?: NexusPrismaScalarOpts) => NexusPrismaFields // Return optional scalar opts + : IsModelNameExistsInGraphQLTypes extends true // If model name has a mapped graphql types + ? ( + opts?: NexusPrismaRelationOpts + ) => NexusPrismaFields // Then make opts optional + : ( + opts: NexusPrismaRelationOpts + ) => NexusPrismaFields; // Else force use input the related graphql type -> { type: '...' } + +type GetNexusPrismaMethod< + TypeName extends string +> = TypeName extends keyof NexusPrismaMethods + ? NexusPrismaMethods[TypeName] + : ( + typeName: CustomTypeName + ) => NexusPrismaMethods[CustomTypeName]; + +type GetNexusPrisma< + TypeName extends string, + ModelOrCrud extends 'model' | 'crud' +> = ModelOrCrud extends 'model' + ? TypeName extends 'Mutation' + ? never + : TypeName extends 'Query' + ? never + : GetNexusPrismaMethod + : ModelOrCrud extends 'crud' + ? TypeName extends 'Mutation' + ? GetNexusPrismaMethod + : TypeName extends 'Query' + ? GetNexusPrismaMethod + : never + : never; + + +// Generated +interface ModelTypes { + ModelName: photon.ModelName +} + +interface NexusPrismaInputs { + Query: { + modelNames: { + filtering: 'id' | 'name' | 'AND' | 'OR' | 'NOT' + ordering: 'id' | 'name' +} + + }, + ModelName: { + + + } +} + +interface NexusPrismaTypes { + Query: { + modelName: 'ModelName' + modelNames: 'ModelName' + + }, + Mutation: { + createOneModelName: 'ModelName' + updateOneModelName: 'ModelName' + updateManyModelName: 'BatchPayload' + deleteOneModelName: 'ModelName' + deleteManyModelName: 'BatchPayload' + upsertOneModelName: 'ModelName' + + }, + ModelName: { + id: 'Int' + name: 'String' + +} +} + +interface NexusPrismaMethods { + ModelName: NexusPrismaFields<'ModelName'> + Query: NexusPrismaFields<'Query'> + Mutation: NexusPrismaFields<'Mutation'> +} + + +declare global { + type NexusPrisma< + TypeName extends string, + ModelOrCrud extends 'model' | 'crud' + > = GetNexusPrisma; +} + " +`; diff --git a/tests/naming.test.ts b/tests/naming.test.ts new file mode 100644 index 000000000..717c00cbf --- /dev/null +++ b/tests/naming.test.ts @@ -0,0 +1,45 @@ +import * as Nexus from 'nexus' +import { generateSchemaAndTypes } from './__utils' + +it('generates publishers pluralized & camel-cased', async () => { + const datamodel = ` + model ModelName { + id Int @id + name String + } +` + + const ModelName = Nexus.objectType({ + name: 'ModelName', + definition(t: any) { + t.model.id() + }, + }) + const Query = Nexus.objectType({ + name: 'Query', + definition(t: any) { + t.crud.modelName() + t.crud.modelNames() + }, + }) + + const Mutation = Nexus.objectType({ + name: 'Mutation', + definition(t: any) { + t.crud.createOneModelName() + t.crud.deleteOneModelName() + t.crud.updateOneModelName() + t.crud.upsertOneModelName() + t.crud.updateManyModelName() + }, + }) + + const { schema, typegen } = await generateSchemaAndTypes(datamodel, [ + Query, + Mutation, + ModelName, + ]) + + expect(schema).toMatchSnapshot('schema') + expect(typegen).toMatchSnapshot('typegen') +})