Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved consistency of handling results of different types returned by custom handlers in CRUD resolvers:
+ Wrap only objects (i.e. not primitive types or arrays) returned by custom handlers in arrays in create, read, and update resolvers
+ Delete mutations return the length of an array that is returned by a `DELETE` custom handler or 1 if a single object is returned
- Don't generate fields for key elements in update input objects

### Fixed

Expand Down
16 changes: 12 additions & 4 deletions lib/schema/args/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const { shouldElementBeIgnored } = require('../util')
const { cdsToGraphQLScalarType } = require('../types/scalar')

module.exports = cache => {
const entityToInputObjectType = (entity, suffix) => {
const entityToInputObjectType = (entity, isUpdate) => {
const suffix = isUpdate ? '_U' : '_C'
const entityName = gqlName(entity.name) + suffix

const cachedEntityInputObjectType = cache.get(entityName)
Expand All @@ -16,19 +17,26 @@ module.exports = cache => {

for (const name in entity.elements) {
const element = entity.elements[name]
const type = _elementToInputObjectType(element, suffix)
const type = _elementToInputObjectType(element, isUpdate)
if (type) fields[gqlName(name)] = { type }
}

// fields is empty if update input object is generated for an entity that only contains key elements
if (Object.keys(fields).length === 0) return

return newEntityInputObjectType
}

const _elementToInputObjectType = (element, suffix) => {
const _elementToInputObjectType = (element, isUpdate) => {
if (shouldElementBeIgnored(element)) return

// No keys in update input object
if (isUpdate && element.key) return

const gqlScalarType = cdsToGraphQLScalarType(element)
if (element.isAssociation || element.isComposition) {
const type = gqlScalarType || entityToInputObjectType(element._target, suffix)
// Input objects in deep updates overwrite previous entries with new entries and therefore always act as create input objects
const type = gqlScalarType || entityToInputObjectType(element._target, false)
return element.is2one ? type : new GraphQLList(type)
} else if (gqlScalarType) {
return gqlScalarType
Expand Down
68 changes: 49 additions & 19 deletions lib/schema/mutation.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ module.exports = cache => {

const serviceName = gqlName(service.name)
const resolve = resolvers[serviceName]
fields[serviceName] = { type: _serviceToObjectType(service), resolve }
const type = _serviceToObjectType(service)
if (type) fields[serviceName] = { type, resolve }
}

if (Object.keys(fields).length === 0) return

return new GraphQLObjectType({ name: 'Mutation', fields })
}

Expand All @@ -28,40 +31,67 @@ module.exports = cache => {
for (const key in service.entities) {
const entity = service.entities[key]
const entityName = gqlName(key)

fields[entityName] = { type: _entityToObjectType(entity) }
const type = _entityToObjectType(entity)
if (type) fields[entityName] = { type }
}

if (Object.keys(fields).length === 0) return

return new GraphQLObjectType({ name: gqlName(service.name) + '_input', fields })
}

const _entityToObjectType = entity => {
// Filter out undefined fields
const fields = Object.fromEntries(
Object.entries({
create: _create(entity),
update: _update(entity),
delete: _delete(entity)
}).filter(([_, v]) => v)
)

if (Object.keys(fields).length === 0) return

return new GraphQLObjectType({ name: gqlName(entity.name) + '_input', fields })
}

const _create = entity => {
const entityObjectType = objectGenerator(cache).entityToObjectType(entity)

const createInputObjectType = inputObjectGenerator(cache).entityToInputObjectType(entity, '_C')
const updateInputObjectType = inputObjectGenerator(cache).entityToInputObjectType(entity, '_U')
const filterInputObjectType = filterGenerator(cache).generateFilterForEntity(entity)
const createInputObjectType = inputObjectGenerator(cache).entityToInputObjectType(entity, false)
if (!createInputObjectType) return

const createArgs = {
const args = {
[ARGS.input]: { type: new GraphQLNonNull(new GraphQLList(createInputObjectType)) }
}
const updateArgs = {
return { type: new GraphQLList(entityObjectType), args }
}

const _update = entity => {
const entityObjectType = objectGenerator(cache).entityToObjectType(entity)

const filterInputObjectType = filterGenerator(cache).generateFilterForEntity(entity)
const updateInputObjectType = inputObjectGenerator(cache).entityToInputObjectType(entity, true)
// filterInputObjectType is undefined if the entity only contains elements that are associations or compositions
// updateInputObjectType is undefined if it is generated for an entity that only contains key elements
if (!filterInputObjectType || !updateInputObjectType) return

const args = {
[ARGS.filter]: { type: filterInputObjectType },
[ARGS.input]: { type: new GraphQLNonNull(updateInputObjectType) }
}
const deleteArgs = {}
return { type: new GraphQLList(entityObjectType), args }
}

if (filterInputObjectType) {
updateArgs[ARGS.filter] = { type: filterInputObjectType }
deleteArgs[ARGS.filter] = { type: filterInputObjectType }
}
const _delete = entity => {
const filterInputObjectType = filterGenerator(cache).generateFilterForEntity(entity)
// filterInputObjectType is undefined if the entity only contains elements that are associations or compositions
if (!filterInputObjectType) return

const fields = {
create: { type: new GraphQLList(entityObjectType), args: createArgs },
update: { type: new GraphQLList(entityObjectType), args: updateArgs },
delete: { type: GraphQLInt, args: deleteArgs }
const args = {
[ARGS.filter]: { type: filterInputObjectType }
}

return new GraphQLObjectType({ name: gqlName(entity.name) + '_input', fields })
return { type: GraphQLInt, args }
}

return { generateMutationObjectType }
Expand Down
7 changes: 3 additions & 4 deletions lib/schema/types/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ module.exports = cache => {
// REVISIT: requires differentiation for support of configurable schema flavors
const type = element.is2many ? _elementToObjectConnectionType(element) : _elementToObjectType(element)
if (type) {
fields[gqlName(name)] = {
type,
...(element.is2many && { args: argsGenerator(cache).generateArgumentsForType(element) })
}
const field = { type }
if (element.is2many) field.args = argsGenerator(cache).generateArgumentsForType(element)
fields[gqlName(name)] = field
}
}

Expand Down
70 changes: 22 additions & 48 deletions test/schemas/bookshop-graphql.gql
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ input AdminService_Authors_C {
}

input AdminService_Authors_U {
ID: Int
books: [AdminService_Books_U]
books: [AdminService_Books_C]
createdAt: Timestamp
createdBy: String
dateOfBirth: Date
Expand Down Expand Up @@ -193,23 +192,22 @@ input AdminService_Books_C {
}

input AdminService_Books_U {
ID: Int
author: AdminService_Authors_U
author: AdminService_Authors_C
author_ID: Int
chapters: [AdminService_Chapters_U]
chapters: [AdminService_Chapters_C]
createdAt: Timestamp
createdBy: String
currency: AdminService_Currencies_U
currency: AdminService_Currencies_C
currency_code: String
descr: String
genre: AdminService_Genres_U
genre: AdminService_Genres_C
genre_ID: Int
image: Binary
modifiedAt: Timestamp
modifiedBy: String
price: Decimal
stock: Int
texts: [AdminService_Books_texts_U]
texts: [AdminService_Books_texts_C]
title: String
}

Expand Down Expand Up @@ -278,9 +276,7 @@ input AdminService_Books_texts_C {
}

input AdminService_Books_texts_U {
ID: Int
descr: String
locale: String
title: String
}

Expand Down Expand Up @@ -339,13 +335,10 @@ input AdminService_Chapters_C {
}

input AdminService_Chapters_U {
book: AdminService_Books_U
book_ID: Int
createdAt: Timestamp
createdBy: String
modifiedAt: Timestamp
modifiedBy: String
number: Int
title: String
}

Expand Down Expand Up @@ -411,12 +404,11 @@ input AdminService_Currencies_C {
}

input AdminService_Currencies_U {
code: String
descr: String
minorUnit: Int16
name: String
symbol: String
texts: [AdminService_Currencies_texts_U]
texts: [AdminService_Currencies_texts_C]
}

type AdminService_Currencies_connection {
Expand Down Expand Up @@ -468,9 +460,7 @@ input AdminService_Currencies_texts_C {
}

input AdminService_Currencies_texts_U {
code: String
descr: String
locale: String
name: String
}

Expand Down Expand Up @@ -537,13 +527,12 @@ input AdminService_Genres_C {
}

input AdminService_Genres_U {
ID: Int
children: [AdminService_Genres_U]
children: [AdminService_Genres_C]
descr: String
name: String
parent: AdminService_Genres_U
parent: AdminService_Genres_C
parent_ID: Int
texts: [AdminService_Genres_texts_U]
texts: [AdminService_Genres_texts_C]
}

type AdminService_Genres_connection {
Expand Down Expand Up @@ -593,9 +582,7 @@ input AdminService_Genres_texts_C {
}

input AdminService_Genres_texts_U {
ID: Int
descr: String
locale: String
name: String
}

Expand Down Expand Up @@ -750,20 +737,19 @@ input CatalogService_Books_C {
}

input CatalogService_Books_U {
ID: Int
author: String
chapters: [CatalogService_Chapters_U]
chapters: [CatalogService_Chapters_C]
createdAt: Timestamp
currency: CatalogService_Currencies_U
currency: CatalogService_Currencies_C
currency_code: String
descr: String
genre: CatalogService_Genres_U
genre: CatalogService_Genres_C
genre_ID: Int
image: Binary
modifiedAt: Timestamp
price: Decimal
stock: Int
texts: [CatalogService_Books_texts_U]
texts: [CatalogService_Books_texts_C]
title: String
}

Expand Down Expand Up @@ -828,9 +814,7 @@ input CatalogService_Books_texts_C {
}

input CatalogService_Books_texts_U {
ID: Int
descr: String
locale: String
title: String
}

Expand Down Expand Up @@ -889,13 +873,10 @@ input CatalogService_Chapters_C {
}

input CatalogService_Chapters_U {
book: CatalogService_Books_U
book_ID: Int
createdAt: Timestamp
createdBy: String
modifiedAt: Timestamp
modifiedBy: String
number: Int
title: String
}

Expand Down Expand Up @@ -961,12 +942,11 @@ input CatalogService_Currencies_C {
}

input CatalogService_Currencies_U {
code: String
descr: String
minorUnit: Int16
name: String
symbol: String
texts: [CatalogService_Currencies_texts_U]
texts: [CatalogService_Currencies_texts_C]
}

type CatalogService_Currencies_connection {
Expand Down Expand Up @@ -1018,9 +998,7 @@ input CatalogService_Currencies_texts_C {
}

input CatalogService_Currencies_texts_U {
code: String
descr: String
locale: String
name: String
}

Expand Down Expand Up @@ -1087,13 +1065,12 @@ input CatalogService_Genres_C {
}

input CatalogService_Genres_U {
ID: Int
children: [CatalogService_Genres_U]
children: [CatalogService_Genres_C]
descr: String
name: String
parent: CatalogService_Genres_U
parent: CatalogService_Genres_C
parent_ID: Int
texts: [CatalogService_Genres_texts_U]
texts: [CatalogService_Genres_texts_C]
}

type CatalogService_Genres_connection {
Expand Down Expand Up @@ -1143,9 +1120,7 @@ input CatalogService_Genres_texts_C {
}

input CatalogService_Genres_texts_U {
ID: Int
descr: String
locale: String
name: String
}

Expand Down Expand Up @@ -1226,19 +1201,18 @@ input CatalogService_ListOfBooks_C {
}

input CatalogService_ListOfBooks_U {
ID: Int
author: String
chapters: [CatalogService_Chapters_U]
chapters: [CatalogService_Chapters_C]
createdAt: Timestamp
currency: CatalogService_Currencies_U
currency: CatalogService_Currencies_C
currency_code: String
genre: CatalogService_Genres_U
genre: CatalogService_Genres_C
genre_ID: Int
image: Binary
modifiedAt: Timestamp
price: Decimal
stock: Int
texts: [CatalogService_Books_texts_U]
texts: [CatalogService_Books_texts_C]
title: String
}

Expand Down
Loading