Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ module.exports = {
tsConfig: './tsconfig.test.json',
},
},
setupFiles: ['core-js'],
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@typescript-eslint/eslint-plugin": "^2.7.0",
"@typescript-eslint/parser": "^2.7.0",
"add": "^2.0.6",
"core-js": "^3.6.4",
"dotenv": "^8.0.0",
"eslint": "^5.16.0",
"eslint-config-prettier": "^6.3.0",
Expand Down
57 changes: 13 additions & 44 deletions src/args/arg-order-by.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,22 @@
import { SchemaBuilder as NexusSchemaBuilder, enumType } from 'nexus/dist/core'
import { getConnection } from 'typeorm'
import { getEntityTypeName } from '../util'
import { namingStrategy } from '../nexus/naming-strategy'
import { enumType } from 'nexus/dist/core'

export const orderTypes = ['ASC', 'DESC']
export const orderTypes = ['ASC', 'DESC'] as const

export type ArgOrder = string[]
export type ArgOrderType = { [index: string]: typeof orderTypes[number] }

export interface OrderInfo {
propertyName: string
type: 'ASC' | 'DESC'
type: typeof orderTypes[number]
}

export function orderNamesToOrderInfos(orderNames: ArgOrder): OrderInfo[] {
return orderNames.reduce<OrderInfo[]>((result, orderName) => {
const splitted = orderName.split('_')
const { length } = splitted
if (length > 0) {
const type = splitted[splitted.length - 1]
if (type === 'ASC' || type === 'DESC') {
result.push({
propertyName: splitted.slice(0, length - 1).join('_'),
type,
})
}
}

return result
}, [])
export const translateOrderClause = (orderByProperties: ArgOrderType): OrderInfo[] => {
return Object.keys(orderByProperties).map(propertyName => ({
propertyName,
type: orderByProperties[propertyName],
}))
}

export const createOrderByInputObjectType = (
entity: any,
nexusBuilder: NexusSchemaBuilder,
): string => {
const { columns: entityColumns } = getConnection().getMetadata(entity)
const typeName = namingStrategy.orderByInputType(getEntityTypeName(entity))
const members: string[] = []
entityColumns.forEach(column => {
orderTypes.forEach(orderType => {
members.push(`${column.propertyName}_${orderType}`)
})
})
nexusBuilder.addType(
enumType({
name: typeName,
members,
}),
)

return typeName
}
export const OrderByArgument = enumType({
name: 'OrderByArgument',
members: orderTypes.reduce((object, value) => ({ ...object, [value]: value }), {}),
})
84 changes: 60 additions & 24 deletions src/args/arg-where.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getConnection } from 'typeorm'
import { inputObjectType } from 'nexus'

export const singleOperandOperations = ['contains']
export const numberOperandOperations = ['lt', 'lte', 'gt', 'gte']
Expand Down Expand Up @@ -55,36 +56,71 @@ export function translateWhereClause(alias: string, where: any, idx = 0): Transl
return
}

idx += 1
const value = where[key]
const [fieldName, operation] = key.split('_')
const paramName = `${fieldName}${idx}`
translated.params[paramName] = value

if (translated.expression) {
translated.expression += ' AND '
if (typeof where[key] !== 'object') {
where[key] = { equals: where[key] }
}
const columnSelection = `${escape(alias)}.${escape(fieldName)}`

if (operation === 'contains') {
translated.params[paramName] = `%${translated.params[paramName]}%`
translated.expression += `${columnSelection} LIKE :${paramName}`
return
}
for (const operation in where[key]) {
idx += 1

const operator = numberOperandOperationToOperator(operation)
if (operator) {
translated.expression += `${columnSelection} ${operator} :${paramName}`
return
}
const value = where[key][operation]
const fieldName = key

if (operation === 'in') {
translated.expression += `${columnSelection} IN (:...${paramName})`
return
}
const paramName = `${fieldName}${idx}`
translated.params[paramName] = value

if (translated.expression) {
translated.expression += ' AND '
}
const columnSelection = `${escape(alias)}.${escape(fieldName)}`

translated.expression += `${columnSelection} = :${paramName}`
if (operation === 'contains') {
translated.params[paramName] = `%${translated.params[paramName]}%`
translated.expression += `${columnSelection} LIKE :${paramName}`
continue
}

const operator = numberOperandOperationToOperator(operation)
if (operator) {
translated.expression += `${columnSelection} ${operator} :${paramName}`
continue
}

if (operation === 'in') {
translated.expression += `${columnSelection} IN (:...${paramName})`
continue
}

translated.expression += `${columnSelection} = :${paramName}`
continue
}
})

return translated
}

export const StringFilter = inputObjectType({
name: 'StringFilter',
definition(t) {
t.string('equals')
singleOperandOperations.map(operator => t.string(operator))
multipleOperandOperations.map(operator => t.list.string(operator))
},
})

export const IntFilter = inputObjectType({
name: 'IntFilter',
definition(t) {
t.int('equals')
numberOperandOperations.map(operator => t.int(operator))
multipleOperandOperations.map(operator => t.list.int(operator))
},
})

export const IdFilter = inputObjectType({
name: 'IdFilter',
definition(t) {
t.id('equals')
multipleOperandOperations.map(operator => t.list.id(operator))
},
})
105 changes: 72 additions & 33 deletions src/entity-type-def-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@ import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'
import { getConnection, EntityMetadata } from 'typeorm'
import { SchemaBuilder as NexusSchemaBuilder, inputObjectType, enumType } from 'nexus/dist/core'
import { typeORMColumnTypeToGraphQLType } from './type'
import {
singleOperandOperations,
numberOperandOperations,
multipleOperandOperations,
} from './args/arg-where'
import { getDatabaseObjectMetadata } from './decorators'
import { namingStrategy } from './nexus/naming-strategy'
import { getEntityTypeName } from './util'
import { orderTypes } from './args/arg-order-by'
import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'
import * as Nexus from 'nexus'

Expand Down Expand Up @@ -161,7 +155,9 @@ export class EntityTypeDefManager {
name: typeName,
definition: t => {
t.field('connect', {
type: this.useWhereInputType(relatedEntity, nexusBuilder),
type: isRelationAnArray
? this.useWhereInputType(relatedEntity, nexusBuilder)
: this.useWhereUniqueInputType(relatedEntity, nexusBuilder),
required: false,
})
t.field('create', {
Expand Down Expand Up @@ -348,30 +344,76 @@ export class EntityTypeDefManager {
nexusBuilder,
) as Nexus.core.AllInputTypes
if (columnTypeName === 'String') {
singleOperandOperations.forEach(singleOperandOperation => {
t.field(`${column.propertyName}_${singleOperandOperation}`, {
type: columnTypeName!,
})
t.field(column.propertyName, {
type: 'StringFilter',
})
} else if (columnTypeName === 'Int' || columnTypeName === 'Float') {
numberOperandOperations.forEach(numberOperandOperation => {
t.field(`${column.propertyName}_${numberOperandOperation}`, {
type: columnTypeName!,
})
t.field(column.propertyName, {
type: 'IntFilter',
})
} else if (columnTypeName === 'ID') {
t.field(column.propertyName, {
type: 'IdFilter',
})
} else {
t.field(column.propertyName, {
type: columnTypeName,
})
}
})
},
}),
)

// Define ${arg}_${operand}: Value[]
multipleOperandOperations.forEach(multipleOperandOperation => {
t.field(`${column.propertyName}_${multipleOperandOperation}`, {
type: columnTypeName!,
list: true,
})
})
this.typesDictionary.push(typeName)
return typeName
}

t.field(column.propertyName, {
type: columnTypeName,
})
useWhereUniqueInputType(
entity: Function,
nexusBuilder: NexusSchemaBuilder,
): Nexus.core.AllInputTypes {
const entityTypeName = getEntityTypeName(entity)
const typeName = namingStrategy.whereUniqueInputType(entityTypeName) as Nexus.core.AllInputTypes

if (this.typesDictionary.includes(typeName)) {
return typeName
}

const { columns: entityColumns, indices, uniques } = getConnection().getMetadata(entity)

// find indices columns, and for now only allow inices that registers only one column
const uniqueIndices = [
indices
.filter(index => index.isUnique)
.map(index => index.columns.map(column => column.propertyName)),
uniques.map(unique => unique.columns.map(column => column.propertyName)),
].flat(10)
Comment on lines +386 to +391

@olup olup Feb 4, 2020

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is misleading : this allows for all unique indexes (even multi columns) AND @column({unique : true})


nexusBuilder.addType(
inputObjectType({
name: typeName,
definition: t => {
entityColumns.forEach(column => {
if (column.relationMetadata && column.isVirtual) {
return
}

const columnTypeName = this.entityColumnToTypeName(
entity,
column,
nexusBuilder,
) as Nexus.core.AllInputTypes

if (columnTypeName === 'ID') {
t.field(column.propertyName, {
type: columnTypeName,
})
} else if (uniqueIndices.includes(column.propertyName)) {
t.field(column.propertyName, {
type: columnTypeName,
})
}
})
},
}),
Expand All @@ -393,16 +435,13 @@ export class EntityTypeDefManager {
}

const { columns: entityColumns } = getConnection().getMetadata(entity)
const members: string[] = []
entityColumns.forEach(column => {
orderTypes.forEach(orderType => {
members.push(`${column.propertyName}_${orderType}`)
})
})

nexusBuilder.addType(
enumType({
inputObjectType({
name: typeName,
members,
definition: t => {
entityColumns.map(column => t.field(column.propertyName, { type: 'OrderByArgument' }))
},
}),
)

Expand Down
4 changes: 2 additions & 2 deletions src/nexus/crud/find-many-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import { OutputPropertyFactoryConfig } from 'nexus/dist/dynamicProperty'
import { GraphQLResolveInfo } from 'graphql'
import { OverrideQueryBuilderConfigFn, CRUDFieldConfigResolveFn } from '../crud-field-output-method'
import { intArg } from 'nexus'
import { ArgOrderType } from '../../args/arg-order-by'

interface FindManyResolverArgs {
where?: ArgWhereType
first?: number
last?: number
skip?: number
orderBy?: string[]
orderBy?: ArgOrderType
}

export interface FindManyFieldNextFnExtraContext {
Expand Down Expand Up @@ -72,7 +73,6 @@ export function defineFindManyField(
where: arg({ type: manager.useWhereInputType(entity, builder) }),
orderBy: arg({
type: manager.useOrderByInputType(entity, builder),
list: true,
}),
}

Expand Down
9 changes: 3 additions & 6 deletions src/nexus/crud/find-one-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { EntityTypeDefManager } from '../../entity-type-def-manager'
import { OutputPropertyFactoryConfig } from 'nexus/dist/dynamicProperty'
import { GraphQLResolveInfo } from 'graphql'
import { OverrideQueryBuilderConfigFn, CRUDFieldConfigResolveFn } from '../crud-field-output-method'
import { ArgOrderType } from '../../args/arg-order-by'

interface FindOneResolverArgs {
where?: ArgWhereType
orderBy?: string[]
orderBy?: ArgOrderType
}

export interface FindOneFieldNextFnExtraContext {
Expand Down Expand Up @@ -59,11 +60,7 @@ export function defineFindOneField(
}

let args: ArgsRecord = {
where: arg({ type: manager.useWhereInputType(entity, builder) }),
orderBy: arg({
type: manager.useOrderByInputType(entity, builder),
list: true,
}),
where: arg({ type: manager.useWhereUniqueInputType(entity, builder) }),
...config.args,
}
if (config && config.args) {
Expand Down
1 change: 1 addition & 0 deletions src/nexus/naming-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const namingStrategy = {
createInputType: (entityName: string) => `${makeFirstLetterUpperCase(entityName)}CreateInput`,
updateInputType: (entityName: string) => `${makeFirstLetterUpperCase(entityName)}UpdateInput`,
whereInputType: (entityName: string) => `${entityName}WhereInput`,
whereUniqueInputType: (entityName: string) => `${entityName}WhereUniqueInput`,
orderByInputType: (entityName: string) => `${entityName}OrderByInput`,
enumType: (entityName: string, propertyName: string) =>
`${entityName}${makeFirstLetterUpperCase(propertyName)}Enum`,
Expand Down
Loading