Skip to content

Commit

Permalink
feat(graphql): add collection update mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
calebmer committed Oct 9, 2016
1 parent 69f16ad commit 1d3eb5d
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GraphQLFieldConfig } from 'graphql'
import { Collection } from '../../../interface'
import BuildToken from '../BuildToken'
import createCreateCollectionMutationFieldEntry from './mutations/createCreateCollectionMutationFieldEntry'
import createUpdateCollectionMutationFieldEntry from './mutations/createUpdateCollectionMutationFieldEntry'
import createDeleteCollectionMutationFieldEntry from './mutations/createDeleteCollectionMutationFieldEntry'
import createDeleteCollectionKeyMutationFieldEntry from './mutations/createDeleteCollectionKeyMutationFieldEntry'

Expand All @@ -19,6 +20,8 @@ export default function createCollectionMutationFieldEntries (
const optionalEntries: Array<[string, GraphQLFieldConfig<mixed, mixed>] | undefined> = [
// Add the create collection mutation.
createCreateCollectionMutationFieldEntry(buildToken, collection),
// Add the update collection mutation. Uses the collection鈥檚 primary key.
createUpdateCollectionMutationFieldEntry(buildToken, collection),
// Add the delete collection mutation. Uses the collection鈥檚 primary key.
createDeleteCollectionMutationFieldEntry(buildToken, collection),
// Add the delete mutation for all of the collection keys.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import BuildToken from '../../BuildToken'
import createMutationField from '../../createMutationField'
import getCollectionType from '../getCollectionType'
import createCollectionKeyInputHelpers from '../createCollectionKeyInputHelpers'
import { createDeleteCollectionOutputFieldEntries } from './createDeleteCollectionMutationFieldEntry'

/**
* Creates a delete mutation which will delete a single value from a collection
Expand All @@ -25,24 +26,8 @@ export default function createDeleteCollectionKeyMutationFieldEntry <TKey>(

return [formatName.field(name), createMutationField<ObjectType.Value>(buildToken, {
name,
// Add the input fields from our input helpers.
inputFields: inputHelpers.fieldEntries,
outputFields: [
// Add the deleted value as an output field so the user can see the
// object they just deleted.
[formatName.field(collection.type.name), {
type: getCollectionType(buildToken, collection),
resolve: value => value,
}],
// Add the deleted values globally unique id as well. This one is
// especially useful for removing old nodes from the cache.
collection.primaryKey && [formatName.field(`deleted-${collection.type.name}-id`), {
type: GraphQLID,
resolve: value => idSerde.serialize(collection.primaryKey!, collection.primaryKey!.getKeyFromValue(value))
}],
],
// Actually delete the value getting the key from our input with our
// helpers.
outputFields: createDeleteCollectionOutputFieldEntries(buildToken, collection),
execute: (context, input) =>
collectionKey.delete!(context, inputHelpers.getKey(input)),
})]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,7 @@ export default function createDeleteCollectionMutationFieldEntry (
type: new GraphQLNonNull(GraphQLID),
}],
],
outputFields: [
// Add the deleted value as an output field so the user can see the
// object they just deleted.
[formatName.field(collection.type.name), {
type: getCollectionType(buildToken, collection),
resolve: value => value,
}],
// Add the deleted values globally unique id as well. This one is
// especially useful for removing old nodes from the cache.
[formatName.field(`deleted-${collection.type.name}-id`), {
type: GraphQLID,
resolve: value => idSerde.serialize(primaryKey, primaryKey.getKeyFromValue(value))
}],
],
outputFields: createDeleteCollectionOutputFieldEntries(buildToken, collection),
// Execute by deserializing the id into its component parts and delete a
// value in the collection using that key.
execute: (context, input) => {
Expand All @@ -60,3 +47,28 @@ export default function createDeleteCollectionMutationFieldEntry (
},
})]
}

/**
* Creates the output fields returned by the collection delete mutation.
*/
export function createDeleteCollectionOutputFieldEntries (
buildToken: BuildToken,
collection: Collection,
): Array<[string, GraphQLFieldConfig<ObjectType.Value, mixed>] | null> {
const { primaryKey } = collection

return [
// Add the deleted value as an output field so the user can see the
// object they just deleted.
[formatName.field(collection.type.name), {
type: getCollectionType(buildToken, collection),
resolve: value => value,
}],
// Add the deleted values globally unique id as well. This one is
// especially useful for removing old nodes from the cache.
primaryKey ? [formatName.field(`deleted-${collection.type.name}-id`), {
type: GraphQLID,
resolve: value => idSerde.serialize(primaryKey, primaryKey.getKeyFromValue(value)),
}] : null,
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
GraphQLInputType,
GraphQLNonNull,
GraphQLID,
GraphQLInputObjectType,
GraphQLInputFieldConfig,
GraphQLFieldConfig,
getNullableType,
} from 'graphql'
import { Collection, ObjectType } from '../../../../interface'
import { formatName, buildObject, idSerde, memoize2 } from '../../../utils'
import BuildToken from '../../BuildToken'
import getType from '../../getType'
import createMutationField from '../../createMutationField'
import transformInputValue, { $$inputValueKeyName } from '../../transformInputValue'
import getCollectionType from '../getCollectionType'

/**
* Creates a delete mutation that uses the primary key of a collection and an
* object鈥檚 global GraphQL identifier to delete a value in the collection.
*/
// TODO: test
export default function createDeleteCollectionMutationFieldEntry (
buildToken: BuildToken,
collection: Collection,
): [string, GraphQLFieldConfig<mixed, mixed>] | undefined {
const { primaryKey } = collection

// If there is no primary key, or the primary key has no delete method. End
// early.
if (!primaryKey || !primaryKey.update)
return

const { options, inventory } = buildToken
const name = `update-${collection.type.name}`
const patchFieldName = formatName.field(`${collection.type.name}-patch`)
const patchType = getCollectionPatchType(buildToken, collection)

return [formatName.field(name), createMutationField<ObjectType.Value>(buildToken, {
name,
inputFields: [
// The only input field we want is the globally unique id which
// corresponds to the primary key of this collection.
[options.nodeIdFieldName, {
// TODO: description
type: new GraphQLNonNull(GraphQLID),
}],
// Also include the patch object type. This is its own object type so
// that people can just have a single patch object and not need to rename
// keys. This also means users can freely upload entire objects to this
// field.
[patchFieldName, {
// TODO: description
type: new GraphQLNonNull(patchType),
}],
],
outputFields: createUpdateCollectionOutputFieldEntries(buildToken, collection),
// Execute by deserializing the id into its component parts and delete a
// value in the collection using that key.
execute: (context, input) => {
const { collectionKey, keyValue } = idSerde.deserialize(inventory, input[options.nodeIdFieldName] as string)

if (collectionKey !== primaryKey)
throw new Error(`The provided id is for collection '${collectionKey.collection.name}', not the expected collection '${collection.name}'.`)

// Get the patch from our input.
const patch = transformInputValue(patchType, input[patchFieldName])

return primaryKey.update!(context, keyValue, patch as any)
},
})]
}

/**
* Gets the patch type for a collection. The patch type allows us to define
* fine-grained changes to our object and is very similar to the input object
* type.
*/
export const getCollectionPatchType = memoize2(createCollectionPatchType)

/**
* The internal un-memoized implementation of `getCollectionPatchType`.
*
* @private
*/
function createCollectionPatchType (buildToken: BuildToken, collection: Collection): GraphQLInputObjectType<mixed> {
const { type } = collection
return new GraphQLInputObjectType({
name: formatName.type(`${type.name}-patch`),
// TODO: description
fields: () => buildObject<GraphQLInputFieldConfig<mixed>>(
Array.from(type.fields).map<[string, GraphQLInputFieldConfig<mixed>]>(([fieldName, field]) =>
[formatName.field(fieldName), {
// TODO: description
type: getNullableType(getType(buildToken, field.type, true)) as GraphQLInputType<mixed>,
[$$inputValueKeyName]: fieldName,
}]
)
),
})
}

/**
* Creates the output fields returned by the collection update mutation.
*/
export function createUpdateCollectionOutputFieldEntries (
buildToken: BuildToken,
collection: Collection,
): Array<[string, GraphQLFieldConfig<ObjectType.Value, mixed>]> {
return [
// Add the updated value as an output field so the user can see the
// object they just updated.
[formatName.field(collection.type.name), {
type: getCollectionType(buildToken, collection),
resolve: value => value,
}],
]
}

0 comments on commit 1d3eb5d

Please sign in to comment.